/**
 * @file Simulation.cc
 * @author Xiaoze Lin (linxiaoze96@gmail.com)
 * @brief The implementation of simulation class.
 * @version 0.1
 *
 * @copyright Copyright (c) 2024
 *
 */

#include "Simulation.hh"

namespace ifsim {

Simulation::Simulation() : _eventManager(NULL)
{
  _netlistManager = NetlistManager::getOrCreateNetlistManager();
}

Simulation::Simulation(FaultManager* fault, FuncPatternManager* func_pat) : _eventManager(NULL)
{
  _netlistManager = NetlistManager::getOrCreateNetlistManager();
  _faultManager = fault;
  _funcPatManager = func_pat;
}

/**
 * @brief Get netlist info.
 */
void Simulation::getNetlistInfo()
{
  _numNodes = _netlistManager->simNetlist().numNodes();
  _numLevels = _netlistManager->simNetlist().numLevels();
  _numPIs = _netlistManager->simNetlist().numPIs();
  _numPOs = _netlistManager->simNetlist().numPOs();
  _numDffs = _netlistManager->simNetlist().numDffs();
  _firstPO = _netlistManager->simNetlist().firstNodePO();
  _firstPI = _netlistManager->simNetlist().firstNodePI();
  _firstDffId = _netlistManager->simNetlist().firstNodeDff();
  _simGatesPtr = &(_netlistManager->simNetlist().nodes()[0]);
}

/**
 * @brief Init gmv.
 */
void Simulation::initGmvAndGetNetlistInfo()
{
  _gates = _netlistManager->simNetlist().oclGatesPtr();
  _preds = _netlistManager->simNetlist().predsPtr();
  _succs = _netlistManager->simNetlist().succsPtr();
  Value parallel_valueX;
  parallel_valueX.setParallelX();

  // init _gmv, _dffMasterVal
  _gmv.assign(_netlistManager->simNetlist().numNodes(), parallel_valueX);
  _gmvPtr = &(_gmv[0]);
  _dffMasterVal.assign(_numDffs, parallel_valueX);
  _dffMasterValPtr = &(_dffMasterVal[0]);

  // init tie nodes
  for (auto tie0_id : _netlistManager->simNetlist().gatesTie0()) {
    _gmvPtr[tie0_id].setParallel0();
  }
  for (auto tie1_id : _netlistManager->simNetlist().gatesTie1()) {
    _gmvPtr[tie1_id].setParallel1();
  }
  for (auto tiez_id : _netlistManager->simNetlist().gatesTiez()) {
    _gmvPtr[tiez_id].setParallelZ();
  }
}

/**
 * @brief Sim good circuit with functional pattern.
 */
bool Simulation::simGoodCktWithFuncPat(bool vec_mode)
{
  QueueBasedEventManager event_mgr0(_netlistManager->simNetlist().numNodes(), 1, _netlistManager->simNetlist().firstNodeInLevel());
  ieda::Stats stats;

  _eventManager = &event_mgr0;

  getNetlistInfo();
  initGmvAndGetNetlistInfo();

  for (int frame_id = 0; frame_id < _funcPatManager->frameNum(); ++frame_id) {
    assignToggledStimuli(frame_id);
    eventDrivenLogicSimWithFuncPat(frame_id);
    comparePoValue(_funcPatManager->getResponse(frame_id));
  }

  double memory_delta = stats.memoryDelta();
  LOG_INFO << "memory usage " << memory_delta << "MB";
  double time_delta = stats.elapsedRunTime();
  LOG_INFO << "time elapsed " << time_delta << "s";

  return true;
}

/**
 * @brief Assign toggled sti.
 */
void Simulation::assignToggledStimuli(int frame_id)
{
  int frame_begin = _funcPatManager->getFrameBegin(frame_id);

  for (uint toggled_i = 0; toggled_i < _funcPatManager->getToggledPISize(frame_id); ++toggled_i) {
    uint gid;
    Value func_val;
    _funcPatManager->getToggledGate(frame_begin + toggled_i, gid, func_val);

    if (updateGateValueWithFuncPat(gid, frame_id, func_val)) {
      addSuccEvents(gid);
    }
  }
}

/**
 * @brief Event driven logic sim with func pat.
 */
void Simulation::eventDrivenLogicSimWithFuncPat(int frame_id)
{
  int gid = -1;
  _eventManager->curLevel(1);  // start simulating events from level 1
  while ((gid = _eventManager->next()) >= 0) {
    uint64_t val0, val1;
    evalGateBasedOnCurrTimeFrame(gid, val0, val1);  // gate evaluation
    Value new_val(val0, val1);
    if (updateGateValueWithFuncPat(gid, frame_id, new_val)) {
      addSuccEvents(gid);
    }
  }
}

/**
 * @brief Compare po value.
 */
bool Simulation::comparePoValue(const std::vector<Value>& po_vals)
{
  for (int po_i = 0; po_i < _numPOs; ++po_i) {
    int po_id = po_i + _firstPO;
    Value po_val = po_vals[po_i];
    uint64_t po_val0 = po_val.bit0(), po_val1 = po_val.bit1();
    uint64_t gmv_val0 = _gmvPtr[po_id].bit0(), gmv_val1 = _gmvPtr[po_id].bit1();
    uint64_t diff_mask = (gmv_val0 ^ po_val0) | (gmv_val1 ^ po_val1);
    if (diff_mask) {
      uint64_t tmp_val0 = diff_mask & po_val0;
      uint64_t tmp_val1 = diff_mask & (~po_val1);
      uint64_t is_diff = ~(tmp_val0 & tmp_val1) & diff_mask;  // skip po_val = X
      if (is_diff) {
        std::cout << TwoSpace << "po id: " << po_id << ", gmv val bit0: " << mask64ToBinary(_gmvPtr[po_id].bit0())
                  << ", bit1: " << mask64ToBinary(_gmvPtr[po_id].bit1()) << ", measure_po val bit0: " << mask64ToBinary(po_val.bit0())
                  << ", bit1: " << mask64ToBinary(po_val.bit1()) << std::endl;
        // return false;
      }
    }
  }
  return true;
}

/**
 * @brief Update gate value in logic sim with func pat.
 */
bool Simulation::updateGateValueWithFuncPat(int gid, int frame_id, Value new_val)
{
  if (_gmvPtr[gid] != new_val) {  // update curr time frame val
    _gmvPtr[gid] = new_val;
    return true;
  }
  return false;
}

/**
 * @brief Add successors events.
 */
void Simulation::addSuccEvents(int gid)
{
  for (int succ_i = 0; succ_i < numSuccs(gid); ++succ_i) {
    int succ_id = succs(gid)[succ_i];
    _eventManager->insert(succ_id, level(succ_id));
  }
}

/**
 * @brief Fault simulation.
 */
bool Simulation::faultSimulate(int num_thread, bool is_bit_parallel, bool vec_mode)
{
  const int* collapsed_fault_ptr = &(_faultManager->cosntCollapsedFaultGroup()[0]);
  const std::vector<Fault>* equiv_fault_ptr = &(_faultManager->equivalentFaultGroup()[0]);
  std::vector<FaultClass> fault_class_after_sim;
  QueueBasedEventManager event_mgr0;
  Value parallel_valueX;
  Value256 parallel_valueX_256;
  ieda::Stats stats;

  _simBuiltFault = _faultManager->readFaultList().size() ? false : true;
  if (_simBuiltFault) {
    fault_class_after_sim.assign(_faultManager->numCollapsedFaults(), UNKNOWN);
  } else {
    fault_class_after_sim.assign(_faultManager->readFaultList().size(), UNKNOWN);
  }
  _numThread = num_thread;
  _eventManager = &event_mgr0;
  parallel_valueX.setParallelX();
  parallel_valueX_256.set256ParallelX();

  // for writing fault list out
  std::ofstream os_detect, os_undetect;
  std::string curr_path = std::filesystem::current_path();
  std::string detect_fault_list = curr_path + "/output/detected.fl";
  std::string undetect_fault_list = curr_path + "/output/undetected.fl";
  os_detect.open(detect_fault_list);
  os_undetect.open(undetect_fault_list);
  LOG_FATAL_IF(!os_detect) << "Write fault list: " << detect_fault_list << " failed.";
  LOG_FATAL_IF(!os_undetect) << "Write fault list: " << undetect_fault_list << " failed.";

  getNetlistInfo();

  faultSimulateWithFuncPat(fault_class_after_sim, is_bit_parallel, vec_mode);

  double time_delta = stats.elapsedRunTime();

  // count num of different class of faults and write faults
  int num_detected_fault = 0, num_pot_fault = 0;
  for (int fault_i = 0; fault_i < fault_class_after_sim.size(); ++fault_i) {
    std::vector<Fault> curr_fault_group;
    if (fault_class_after_sim[fault_i] == DS) {  // DS fault
      if (_simBuiltFault) {
        num_detected_fault += equiv_fault_ptr[collapsed_fault_ptr[fault_i]].size();
        curr_fault_group = equiv_fault_ptr[collapsed_fault_ptr[fault_i]];
      } else {
        num_detected_fault += _faultManager->readFaultList()[fault_i].size();
        curr_fault_group = _faultManager->readFaultList()[fault_i];
      }
      curr_fault_group[0].faultClass(DT);  // rep fault

    } else if (fault_class_after_sim[fault_i] == PT) {  // PT fault
      if (_simBuiltFault) {
        num_pot_fault += equiv_fault_ptr[collapsed_fault_ptr[fault_i]].size();
        curr_fault_group = equiv_fault_ptr[collapsed_fault_ptr[fault_i]];
      } else {
        num_pot_fault += _faultManager->readFaultList()[fault_i].size();
        curr_fault_group = _faultManager->readFaultList()[fault_i];
      }
      curr_fault_group[0].faultClass(PT);  // rep fault

    } else {  // undetected fault
      if (_simBuiltFault) {
        curr_fault_group = equiv_fault_ptr[collapsed_fault_ptr[fault_i]];
      } else {
        curr_fault_group = _faultManager->readFaultList()[fault_i];
      }
      curr_fault_group[0].faultClass(ND);  // rep fault
    }

    // write fault
    for (auto curr_fault : curr_fault_group) {
      std::vector<std::string> tmp_vec;
      writeFault(tmp_vec, curr_fault);
      if (curr_fault_group[0].faultClass() == DT || curr_fault_group[0].faultClass() == PT) {
        for (auto str : tmp_vec) {
          os_detect << str;
        }
      } else {
        for (auto str : tmp_vec) {
          os_undetect << str;
        }
      }
    }
  }

  if (_simBuiltFault) {
    dumpFaultSimInfo(num_detected_fault, num_pot_fault, time_delta, false);
  } else {
    dumpFaultSimInfo(num_detected_fault, num_pot_fault, time_delta, true);
  }

  os_detect.close();
  os_undetect.close();

  return true;
}

/**
 * @brief Dump fault simulation infomation.
 */
void Simulation::dumpFaultSimInfo(int num_det_sim_fault, int num_pot_fault, double collapsed_time, bool is_read_fault_list)
{
  if (is_read_fault_list) {
    int num_full_faults = 0;
    for (auto group : _faultManager->readFaultList()) {
      num_full_faults += group.size();
    }
    float num_det_fault = num_det_sim_fault + 0.5 * (float) num_pot_fault;
    float det_sim_per = (float) num_det_sim_fault / (float) num_full_faults * 100.0;
    float pot_fault_per = (float) num_pot_fault / (float) num_full_faults * 100.0;
    float fault_coverage = num_det_fault / (float) num_full_faults * 100.0;

    std::cout << std::fixed << std::setprecision(2);
    std::cout << "             Statistics Report" << std::endl;
    std::cout << "----------------------------------------" << std::endl;
    std::cout << "Fault Classes                   #faults\n" << std::endl;
    std::cout << "----------------------    --------------" << std::endl;
    std::cout << "  FU (full)             " << std::right << std::setw(10) << num_full_faults << std::endl;
    std::cout << "  --------------------    --------------" << std::endl;
    std::cout << "  DS (det_simulation)   " << std::right << std::setw(6) << num_det_sim_fault << " (" << det_sim_per << "%)" << std::endl;
    std::cout << "  PT (posdet_testable)  " << std::right << std::setw(6) << num_pot_fault << " (" << pot_fault_per << "%)" << std::endl;
    std::cout << "----------------------------------------" << std::endl;
    std::cout << "Coverage" << std::endl;
    std::cout << "  --------------------" << std::endl;
    std::cout << "  fault_coverage                " << std::right << std::setw(6) << fault_coverage << "%" << std::endl;
    std::cout << "----------------------------------------" << std::endl;
    std::cout << "CPU_time (secs)            " << std::right << std::setw(10) << collapsed_time << std::endl;
    std::cout << "----------------------------------------" << std::endl;

  } else {  // fsim based on built fault list
    int num_full_faults = _faultManager->numFullFaults();
    int num_untestable_fault = _faultManager->numUntestableFaults();
    int num_test_fault = num_full_faults - num_untestable_fault;
    float num_det_fault = num_det_sim_fault + 0.5 * (float) num_pot_fault;
    int num_uu_fault = _faultManager->numUnusedFaults();
    int num_ti_fault = _faultManager->numTiedFaults();
    int num_bl_fault = _faultManager->numBlockedFaults();
    float det_sim_per = (float) num_det_sim_fault / (float) num_full_faults * 100.0;
    float untestable_per = (float) num_untestable_fault / (float) num_full_faults * 100.0;
    float uu_per = (float) num_uu_fault / (float) num_full_faults * 100.0;
    float ti_per = (float) num_ti_fault / (float) num_full_faults * 100.0;
    float bl_per = (float) num_bl_fault / (float) num_full_faults * 100.0;
    float pot_fault_per = (float) num_pot_fault / (float) num_full_faults * 100.0;
    float test_coverage = num_det_fault / (float) num_test_fault * 100.0;
    float fault_coverage = num_det_fault / (float) num_full_faults * 100.0;

    std::cout << std::fixed << std::setprecision(2);
    std::cout << "             Statistics Report" << std::endl;
    std::cout << "----------------------------------------" << std::endl;
    std::cout << "Fault Classes                 #faults\n" << std::endl;
    std::cout << "----------------------    --------------" << std::endl;
    std::cout << "  FU (full)             " << std::right << std::setw(10) << num_full_faults << std::endl;
    std::cout << "  --------------------    --------------" << std::endl;
    std::cout << "  DS (det_simulation)   " << std::right << std::setw(6) << num_det_sim_fault << " (" << det_sim_per << "%)" << std::endl;
    std::cout << "  PT (posdet_testable)  " << std::right << std::setw(6) << num_pot_fault << " (" << pot_fault_per << "%)" << std::endl;
    std::cout << "  UT (untestable)       " << std::right << std::setw(6) << num_untestable_fault << " (" << untestable_per << "%)"
              << std::endl;
    std::cout << "----------------------------------------" << std::endl;
    std::cout << "Fault Sub-classes" << std::endl;
    std::cout << "  --------------------" << std::endl;
    std::cout << "  UT (untestable)" << std::endl;
    std::cout << "    UU   (unused)        " << std::right << std::setw(6) << num_uu_fault << " (" << uu_per << "%)" << std::endl;
    std::cout << "    TI   (tied)          " << std::right << std::setw(6) << num_ti_fault << " (" << ti_per << "%)" << std::endl;
    std::cout << "    BL   (blocked)       " << std::right << std::setw(6) << num_bl_fault << " (" << bl_per << "%)" << std::endl;
    std::cout << "----------------------------------------" << std::endl;
    std::cout << "Coverage" << std::endl;
    std::cout << "  --------------------" << std::endl;
    std::cout << "  test_coverage                 " << std::right << std::setw(6) << test_coverage << "%" << std::endl;
    std::cout << "  fault_coverage                " << std::right << std::setw(6) << fault_coverage << "%" << std::endl;
    std::cout << "----------------------------------------" << std::endl;
    std::cout << "CPU_time (secs)            " << std::right << std::setw(10) << collapsed_time << std::endl;
    std::cout << "----------------------------------------" << std::endl;
  }
}

/**
 * @brief Fault simulate with func pat.
 */
bool Simulation::faultSimulateWithFuncPat(std::vector<FaultClass>& fault_class_after_sim, bool is_bit_parallel, bool vec_mode)
{
  std::vector<int> sim_fault_list(_faultManager->collapsedFaultGroup());
  sim_fault_list.resize(_faultManager->numCollapsedFaults() * 2);
  int* collapsed_fault_ptr = &(sim_fault_list[0]);

  const std::vector<Fault>* equiv_fault_ptr = &(_faultManager->equivalentFaultGroup()[0]);
  int ds_fault = 0;
  int num_faults = _simBuiltFault ? _faultManager->numCollapsedFaults() : _faultManager->readFaultList().size();
  int size_one_group = is_bit_parallel ? (vec_mode ? MAX_PARALLEL_FAULTS_256 : MAX_PARALLEL_FAULTS) : 1;

  int max_node_id, max_thread_one_node;
  if (numa_available() != -1) {
    max_node_id = numa_max_node();
    max_thread_one_node = _numThread / (max_node_id + 1);
    max_thread_one_node = (max_thread_one_node == 0) ? 1 : max_thread_one_node;
  } else {
    max_node_id = 0;
    max_thread_one_node = _numThread;
  }

  std::vector<int> fault_i_map(num_faults * 2, -1);
  int* fault_i_map_ptr = &(fault_i_map[0]);
  for (int fault_i = 0; fault_i < num_faults; ++fault_i) {
    fault_i_map_ptr[fault_i] = fault_i;
  }

  initDataStructuresAndPtrForFsimWithFuncPat(vec_mode);

  // fault sim by parallel threads
  int num_faults_to_print_info = (num_faults / size_one_group) / 5 * size_one_group;
  num_faults_to_print_info = (num_faults_to_print_info == 0) ? num_faults : num_faults_to_print_info;
  int num_get_faults = 0;
  int curr_num_faults = _faultManager->numCollapsedFaults();

  omp_set_num_threads(_numThread);
#pragma omp parallel for
  for (int thread_id = 0; thread_id < _numThread; ++thread_id) {
    int curr_node_id = thread_id / max_thread_one_node;
    if (numa_available() != -1) {
      numa_run_on_node(curr_node_id);
    }

    // alloc data structures to different numa node
    allocDataStructuresToNumaNode(thread_id, curr_node_id, vec_mode);

    std::vector<Fault> one_group_faults;
    std::vector<int> one_group_fault_ids;
    int start_fault_id = 0;

    while (true) {  // keep running if there are faults have not been processed
#pragma omp atomic capture
      {
        start_fault_id = num_get_faults;
        num_get_faults += size_one_group;
      }

      if (start_fault_id >= num_faults) {  // all faults have been processed, break the while loop
        break;
      }

      if (((start_fault_id % num_faults_to_print_info) == 0) && (start_fault_id > 0)) {
        std::cout << "------  sim " << start_fault_id << " faults  ------ \n";
      }

      // collect curr group of faults
      for (int fault_i = start_fault_id; fault_i < (start_fault_id + size_one_group); ++fault_i) {
        if (fault_i >= num_faults) {
          break;
        }
        Fault curr_fault = _simBuiltFault ? equiv_fault_ptr[collapsed_fault_ptr[fault_i]][0] : _faultManager->readFaultList()[fault_i][0];
        int curr_fault_gid = curr_fault.gid();

        // skip faults at floating point, must be undetected
        if (numSuccsThread(curr_fault_gid, thread_id) == 0 && typeThread(curr_fault_gid, thread_id) != T_OUTPUT) {
          continue;
        }

        one_group_faults.push_back(curr_fault);
        one_group_fault_ids.push_back(fault_i_map_ptr[fault_i]);
      }

      int curr_group_size = one_group_faults.size();
      int nodetect_start = 0;
      std::vector<FaultClass> is_detected(curr_group_size, UNKNOWN);

      if (vec_mode) {
        injectParallelFaultsByModifiedNetlistInVecMode(one_group_faults, thread_id);
      } else {
        injectParallelFaultsByModifiedNetlist(one_group_faults, thread_id);
      }

      // perform fault sim frame by frame
      for (int frame_id = 0; frame_id < _funcPatManager->frameNum(); ++frame_id) {
        if (vec_mode) {
          assignThreadToggledStimuliInVecMode(frame_id, thread_id);
          eventDrivenFaultSimWithFuncPatInVecMode(frame_id, thread_id, curr_group_size);
          compareParallelFsimPoValInVecMode(is_detected, frame_id, curr_group_size, thread_id);
        } else {
          assignThreadToggledStimuli(frame_id, thread_id);
          eventDrivenFaultSimWithFuncPat(frame_id, thread_id, curr_group_size);
          compareParallelFsimPoVal(is_detected, frame_id, thread_id, curr_group_size);
        }

        // if all the faults in curr group have been detected, stop early
        bool is_all_detect = true;
        for (int status_i = nodetect_start; status_i < curr_group_size; ++status_i) {
          FaultClass status = is_detected[status_i];
          if (status != DS) {
            is_all_detect = false;
            break;
          }
          ++nodetect_start;
        }
        if (is_all_detect) {
          break;
        }
      }

      // reset initial gmv after all the faults have been removed
      if (vec_mode) {
        resetOriSimValInVecMode(thread_id);
      } else {
        resetOriSimVal(thread_id);
      }

      // update status of the curr group of faults after fault sim
      for (int status_i = 0; status_i < curr_group_size; ++status_i) {
        fault_class_after_sim[one_group_fault_ids[status_i]] = is_detected[status_i];
      }

      if (vec_mode) {
        removeParallelFaultsByRecoverNetlistInVecMode(thread_id);
      } else {
        removeParallelFaultsByRecoverNetlist(thread_id);
      }

      one_group_faults.clear();
      one_group_fault_ids.clear();
    }
  }

  freeDataStructuresInNumaNode(vec_mode);

  return true;
}

/**
 * @brief Init data structures and pointers for fsim with func pat.
 */
void Simulation::initDataStructuresAndPtrForFsimWithFuncPat(bool vec_mode)
{
  _oriPredsVecSize = _netlistManager->simNetlist().predsVec().size();
  _oriSuccsVecSize = _netlistManager->simNetlist().succsVec().size();
  _threadModifiedNumNodes.assign(_numThread, _numNodes);
  _threadModifiedNumNodesPtr = &(_threadModifiedNumNodes[0]);
  _threadCurrPredsVecSize.assign(_numThread, _oriPredsVecSize);
  _threadCurrPredsVecSizePtr = &(_threadCurrPredsVecSize[0]);
  _threadCurrSuccsVecSize.assign(_numThread, _oriSuccsVecSize);
  _threadCurrSuccsVecSizePtr = &(_threadCurrSuccsVecSize[0]);

  int num_nodes = (vec_mode) ? (MAX_PARALLEL_FAULTS_256 * 2 + _numNodes) : (MAX_PARALLEL_FAULTS * 2 + _numNodes);
  _eventManager->initPtr(num_nodes, _numThread, _netlistManager->simNetlist().firstNodeInLevel());

  if (vec_mode) {
    _gmv256PPtr = new Value256*[_numThread];
    _dffMasterVal256PPtr = new Value256*[_numThread];
    _storedGmv256PPtr = new Value256*[_numThread];
  } else {
    _gmvPPtr = new Value*[_numThread];
    _dffMasterValPPtr = new Value*[_numThread];
    _storedGmvPPtr = new Value*[_numThread];
  }

  _gatesPPtr = new OclGate*[_numThread];
  _predsPPtr = new int*[_numThread];
  _succsPPtr = new int*[_numThread];
  _changedGmvIdPPtr = new int*[_numThread];
  _isValChangedPPtr = new char*[_numThread];
  _masterChangedGmvIdPPtr = new int*[_numThread];
  _masterIsValChangedPPtr = new char*[_numThread];
  _needComparePoIdsPPtr = new int*[_numThread];
  _isPoNeedComparedPPtr = new char*[_numThread];
}

/**
 * @brief Alloc data structures to numa node.
 */
void Simulation::allocDataStructuresToNumaNode(int thread_id, int curr_node_id, bool vec_mode)
{
  auto ori_gates = _netlistManager->simNetlist().oclGatesPtr();
  auto ori_preds = _netlistManager->simNetlist().predsPtr();
  auto ori_succs = _netlistManager->simNetlist().succsPtr();
  int num_added_gates, num_modified_gates, num_modified_preds, num_modified_succs;

  _eventManager->allocDataStructuresToNumaNode(thread_id, curr_node_id, vec_mode, _netlistManager->simNetlist().firstNodeInLevel());

  if (vec_mode) {
    num_added_gates = MAX_PARALLEL_FAULTS_256 * 2;
    num_modified_gates = _numNodes + MAX_PARALLEL_FAULTS_256 * 2;
    num_modified_preds = _oriPredsVecSize + MAX_PARALLEL_FAULTS_256 * 2;
    num_modified_succs = _oriSuccsVecSize + MAX_PARALLEL_FAULTS_256 * _netlistManager->maxFanout();

    Value256 *gmv256, *master_val256, *store_gmv256, *store_mas_gmv256;
    if (numa_available() != -1) {
      gmv256 = (Value256*) numa_alloc_onnode((_numNodes + num_added_gates) * sizeof(Value256), curr_node_id);
      master_val256 = (Value256*) numa_alloc_onnode(_numDffs * sizeof(Value256), curr_node_id);
      store_gmv256 = (Value256*) numa_alloc_onnode((_numNodes + 1) * sizeof(Value256), curr_node_id);
      store_mas_gmv256 = (Value256*) numa_alloc_onnode(_numDffs * sizeof(Value256), curr_node_id);
    } else {
      gmv256 = (Value256*) malloc((_numNodes + num_added_gates) * sizeof(Value256));
      master_val256 = (Value256*) malloc(_numDffs * sizeof(Value256));
      store_gmv256 = (Value256*) malloc((_numNodes + 1) * sizeof(Value256));
      store_mas_gmv256 = (Value256*) malloc(_numDffs * sizeof(Value256));
    }
    _gmv256PPtr[thread_id] = &(gmv256[0]);
    _dffMasterVal256PPtr[thread_id] = &(master_val256[0]);
    _storedGmv256PPtr[thread_id] = &(store_gmv256[0]);

  } else {
    num_added_gates = MAX_PARALLEL_FAULTS * 2;
    num_modified_gates = _numNodes + MAX_PARALLEL_FAULTS * 2;
    num_modified_preds = _oriPredsVecSize + MAX_PARALLEL_FAULTS * 2;
    num_modified_succs = _oriSuccsVecSize + MAX_PARALLEL_FAULTS * _netlistManager->maxFanout();

    Value *gmv, *master_val, *store_gmv;
    if (numa_available() != -1) {
      gmv = (Value*) numa_alloc_onnode((_numNodes + num_added_gates) * sizeof(Value), curr_node_id);
      master_val = (Value*) numa_alloc_onnode(_numDffs * sizeof(Value), curr_node_id);
      store_gmv = (Value*) numa_alloc_onnode((_numNodes + 1) * sizeof(Value), curr_node_id);
    } else {
      gmv = (Value*) malloc((_numNodes + num_added_gates) * sizeof(Value));
      master_val = (Value*) malloc(_numDffs * sizeof(Value));
      store_gmv = (Value*) malloc((_numNodes + 1) * sizeof(Value));
    }
    _gmvPPtr[thread_id] = &(gmv[0]);
    _dffMasterValPPtr[thread_id] = &(master_val[0]);
    _storedGmvPPtr[thread_id] = &(store_gmv[0]);
  }

  OclGate* gates;
  int *preds, *succs, *changed_gmv_id, *master_changed_gmv_id, *need_compare_po_id;
  char *is_val_changed, *master_is_val_changed, *is_po_need_compare;
  if (numa_available() != -1) {
    gates = (OclGate*) numa_alloc_onnode(num_modified_gates * sizeof(OclGate), curr_node_id);
    preds = (int*) numa_alloc_onnode(num_modified_preds * sizeof(int), curr_node_id);
    succs = (int*) numa_alloc_onnode(num_modified_succs * sizeof(int), curr_node_id);
    changed_gmv_id = (int*) numa_alloc_onnode((_numNodes + 1) * sizeof(int), curr_node_id);
    is_val_changed = (char*) numa_alloc_onnode(_numNodes * sizeof(char), curr_node_id);
    master_changed_gmv_id = (int*) numa_alloc_onnode((_numDffs + 1) * sizeof(int), curr_node_id);
    master_is_val_changed = (char*) numa_alloc_onnode(_numDffs * sizeof(char), curr_node_id);
    need_compare_po_id = (int*) numa_alloc_onnode((_numPOs + 1) * sizeof(int), curr_node_id);
    is_po_need_compare = (char*) numa_alloc_onnode(_numPOs * sizeof(char), curr_node_id);
  } else {
    gates = (OclGate*) malloc(num_modified_gates * sizeof(OclGate));
    preds = (int*) malloc(num_modified_preds * sizeof(int));
    succs = (int*) malloc(num_modified_succs * sizeof(int));
    changed_gmv_id = (int*) malloc((_numNodes + 1) * sizeof(int));
    is_val_changed = (char*) malloc(_numNodes * sizeof(char));
    master_changed_gmv_id = (int*) malloc((_numDffs + 1) * sizeof(int));
    master_is_val_changed = (char*) malloc(_numDffs * sizeof(char));
    need_compare_po_id = (int*) malloc((_numPOs + 1) * sizeof(int));
    is_po_need_compare = (char*) malloc(_numPOs * sizeof(char));
  }
  _gatesPPtr[thread_id] = &(gates[0]);
  _predsPPtr[thread_id] = &(preds[0]);
  _succsPPtr[thread_id] = &(succs[0]);
  _changedGmvIdPPtr[thread_id] = &(changed_gmv_id[0]);
  _changedGmvIdPPtr[thread_id][0] = 0;
  _isValChangedPPtr[thread_id] = &(is_val_changed[0]);
  _masterChangedGmvIdPPtr[thread_id] = &(master_changed_gmv_id[0]);
  _masterChangedGmvIdPPtr[thread_id][0] = 0;
  _masterIsValChangedPPtr[thread_id] = &(master_is_val_changed[0]);
  _needComparePoIdsPPtr[thread_id] = &(need_compare_po_id[0]);
  _needComparePoIdsPPtr[thread_id][0] = 0;
  _isPoNeedComparedPPtr[thread_id] = &(is_po_need_compare[0]);

  // init data structures
  if (vec_mode) {
    for (int gid = 0; gid < (_numNodes + num_added_gates); ++gid) {
      _gmv256PPtr[thread_id][gid].set256ParallelX();
    }
    for (int dff_i = 0; dff_i < _numDffs; ++dff_i) {
      _dffMasterVal256PPtr[thread_id][dff_i].set256ParallelX();
    }
    for (auto tie0_id : _netlistManager->simNetlist().gatesTie0()) {
      _gmv256PPtr[thread_id][tie0_id].set256Parallel0();
    }
    for (auto tie1_id : _netlistManager->simNetlist().gatesTie1()) {
      _gmv256PPtr[thread_id][tie1_id].set256Parallel1();
    }
    for (auto tiez_id : _netlistManager->simNetlist().gatesTiez()) {
      _gmv256PPtr[thread_id][tiez_id].set256ParallelZ();
    }
  } else {
    for (int gid = 0; gid < (_numNodes + num_added_gates); ++gid) {
      _gmvPPtr[thread_id][gid].setParallelX();
    }
    for (int dff_i = 0; dff_i < _numDffs; ++dff_i) {
      _dffMasterValPPtr[thread_id][dff_i].setParallelX();
    }
    for (auto tie0_id : _netlistManager->simNetlist().gatesTie0()) {
      _gmvPPtr[thread_id][tie0_id].setParallel0();
    }
    for (auto tie1_id : _netlistManager->simNetlist().gatesTie1()) {
      _gmvPPtr[thread_id][tie1_id].setParallel1();
    }
    for (auto tiez_id : _netlistManager->simNetlist().gatesTiez()) {
      _gmvPPtr[thread_id][tiez_id].setParallelZ();
    }
  }
  for (int gid = 0; gid < _numNodes; ++gid) {
    _gatesPPtr[thread_id][gid] = ori_gates[gid];
    _isValChangedPPtr[thread_id][gid] = 0;
  }
  for (int pid = 0; pid < _oriPredsVecSize; ++pid) {
    _predsPPtr[thread_id][pid] = ori_preds[pid];
  }
  for (int sid = 0; sid < _oriSuccsVecSize; ++sid) {
    _succsPPtr[thread_id][sid] = ori_succs[sid];
  }
  for (int dff_i = 0; dff_i < _numDffs; ++dff_i) {
    _masterIsValChangedPPtr[thread_id][dff_i] = 0;
  }
  for (int po_i = 0; po_i < _numPOs; ++po_i) {
    _isPoNeedComparedPPtr[thread_id][po_i] = 0;
  }
}

/**
 * @brief Free data structures in numa node.
 */
void Simulation::freeDataStructuresInNumaNode(bool vec_mode)
{
  int num_added_gates, num_modified_gates, num_modified_preds, num_modified_succs;
  if (vec_mode) {
    num_added_gates = MAX_PARALLEL_FAULTS_256 * 2;
    num_modified_gates = _numNodes + MAX_PARALLEL_FAULTS_256 * 2;
    num_modified_preds = _oriPredsVecSize + MAX_PARALLEL_FAULTS_256 * 2;
    num_modified_succs = _oriSuccsVecSize + MAX_PARALLEL_FAULTS_256 * _netlistManager->maxFanout();
  } else {
    num_added_gates = MAX_PARALLEL_FAULTS * 2;
    num_modified_gates = _numNodes + MAX_PARALLEL_FAULTS * 2;
    num_modified_preds = _oriPredsVecSize + MAX_PARALLEL_FAULTS * 2;
    num_modified_succs = _oriSuccsVecSize + MAX_PARALLEL_FAULTS * _netlistManager->maxFanout();
  }

  _eventManager->freeDataStructuresInNumaNode(vec_mode);

  for (int thread_id = 0; thread_id < _numThread; ++thread_id) {
    if (vec_mode) {
      if (numa_available() != -1) {
        numa_free(_gmv256PPtr[thread_id], (_numNodes + num_added_gates) * sizeof(Value));
        numa_free(_dffMasterVal256PPtr[thread_id], _numDffs * sizeof(Value256));
        numa_free(_storedGmv256PPtr[thread_id], (_numNodes + 1) * sizeof(Value256));
      } else {
        free(_gmv256PPtr[thread_id]);
        free(_dffMasterVal256PPtr[thread_id]);
        free(_storedGmv256PPtr[thread_id]);
      }
    } else {
      if (numa_available() != -1) {
        numa_free(_gmvPPtr[thread_id], (_numNodes + num_added_gates) * sizeof(Value));
        numa_free(_dffMasterValPPtr[thread_id], _numDffs * sizeof(Value));
        numa_free(_storedGmvPPtr[thread_id], (_numNodes + 1) * sizeof(Value));
      } else {
        free(_gmvPPtr[thread_id]);
        free(_dffMasterValPPtr[thread_id]);
        free(_storedGmvPPtr[thread_id]);
      }
    }
    if (numa_available() != -1) {
      numa_free(_gatesPPtr[thread_id], num_modified_gates * sizeof(OclGate));
      numa_free(_predsPPtr[thread_id], num_modified_preds * sizeof(int));
      numa_free(_succsPPtr[thread_id], num_modified_succs * sizeof(int));
      numa_free(_changedGmvIdPPtr[thread_id], (_numNodes + 1) * sizeof(int));
      numa_free(_isValChangedPPtr[thread_id], _numNodes * sizeof(char));
      numa_free(_masterChangedGmvIdPPtr[thread_id], (_numDffs + 1) * sizeof(int));
      numa_free(_masterIsValChangedPPtr[thread_id], _numDffs * sizeof(char));
      numa_free(_needComparePoIdsPPtr[thread_id], (_numPOs + 1) * sizeof(int));
      numa_free(_isPoNeedComparedPPtr[thread_id], _numPOs * sizeof(char));
    } else {
      free(_gatesPPtr[thread_id]);
      free(_predsPPtr[thread_id]);
      free(_succsPPtr[thread_id]);
      free(_changedGmvIdPPtr[thread_id]);
      free(_isValChangedPPtr[thread_id]);
      free(_masterChangedGmvIdPPtr[thread_id]);
      free(_masterIsValChangedPPtr[thread_id]);
      free(_needComparePoIdsPPtr[thread_id]);
      free(_isPoNeedComparedPPtr[thread_id]);
    }
  }

  if (vec_mode) {
    delete _gmv256PPtr;
    delete _dffMasterVal256PPtr;
    delete _storedGmv256PPtr;
  } else {
    delete _gmvPPtr;
    delete _dffMasterValPPtr;
    delete _storedGmvPPtr;
  }
  delete _gatesPPtr;
  delete _predsPPtr;
  delete _succsPPtr;
  delete _changedGmvIdPPtr;
  delete _isValChangedPPtr;
  delete _masterChangedGmvIdPPtr;
  delete _masterIsValChangedPPtr;
  delete _needComparePoIdsPPtr;
  delete _isPoNeedComparedPPtr;
}

/**
 * @brief Assign toggled sti in different thread.
 */
void Simulation::assignThreadToggledStimuli(int frame_id, int thread_id)
{
  int frame_begin = _funcPatManager->getFrameBegin(frame_id);

  for (uint toggled_i = 0; toggled_i < _funcPatManager->getToggledPISize(frame_id); ++toggled_i) {
    uint gid;
    Value func_val;
    _funcPatManager->getToggledGate(frame_begin + toggled_i, gid, func_val);

    if (updateThreadGateValueInFsim(gid, thread_id, func_val)) {
      addThreadSuccEvents(gid, thread_id);
    }
  }
}

/**
 * @brief Assign toggled sti in different thread in vec mode.
 */
void Simulation::assignThreadToggledStimuliInVecMode(int frame_id, int thread_id)
{
  int frame_begin = _funcPatManager->getFrameBegin(frame_id);

  for (uint toggled_i = 0; toggled_i < _funcPatManager->getToggledPISize(frame_id); ++toggled_i) {
    uint gid;
    Value func_val;
    Value256 func_val256;
    _funcPatManager->getToggledGate(frame_begin + toggled_i, gid, func_val);
    func_val256.expandValueToValue256(func_val);

    if (updateThreadGateValue256InFsim(gid, thread_id, func_val256)) {
      addThreadSuccEvents(gid, thread_id);
    }
  }
}

/**
 * @brief Event driven fault sim with func pattern.
 */
void Simulation::eventDrivenFaultSimWithFuncPat(int frame_id, int thread_id, int num_faults_in_group)
{
  int gid = -1;
  _eventManager->threadCurLevel(0, thread_id);  // start simulating events from level 0 since level of some added gate is 0
  while ((gid = _eventManager->threadNextEvent(thread_id)) >= 0) {
    uint64_t val0, val1;

    evalGateForThreadFsim(gid, thread_id, num_faults_in_group, val0, val1);  // gate evaluation
    Value new_val(val0, val1);

    if (updateThreadGateValueInFsim(gid, thread_id, new_val)) {
      if (typeThread(gid, thread_id) == T_OUTPUT) {  // PO need to be compared
        int po_i = gid - _firstPO;
        if (_isPoNeedComparedPPtr[thread_id][po_i] == 0) {
          ++_needComparePoIdsPPtr[thread_id][0];
          _needComparePoIdsPPtr[thread_id][_needComparePoIdsPPtr[thread_id][0]] = po_i;
          _isPoNeedComparedPPtr[thread_id][po_i] = 1;
        }
      }
      addThreadSuccEvents(gid, thread_id);
    }
  }
}

/**
 * @brief Event driven fault sim with func pattern in vec mode.
 */
void Simulation::eventDrivenFaultSimWithFuncPatInVecMode(int frame_id, int thread_id, int num_faults_in_group)
{
  int gid = -1;
  _eventManager->threadCurLevel(0, thread_id);  // start simulating events from level 0 since level of some added gate is 0
  while ((gid = _eventManager->threadNextEvent(thread_id)) >= 0) {
    __m256i_wrapper val0, val1;

    evalGateForFsimInVecMode(gid, thread_id, num_faults_in_group, val0, val1);  // gate evaluation
    Value256 new_val(val0, val1);

    if (updateThreadGateValue256InFsim(gid, thread_id, new_val)) {
      if (typeThread(gid, thread_id) == T_OUTPUT) {  // PO need to be compared
        int po_i = gid - _firstPO;
        if (_isPoNeedComparedPPtr[thread_id][po_i] == 0) {
          ++_needComparePoIdsPPtr[thread_id][0];
          _needComparePoIdsPPtr[thread_id][_needComparePoIdsPPtr[thread_id][0]] = po_i;
          _isPoNeedComparedPPtr[thread_id][po_i] = 1;
        }
      }
      addThreadSuccEvents(gid, thread_id);
    }
  }
}

/**
 * @brief Inject parallel faults.
 */
void Simulation::injectParallelFaultsByModifiedNetlist(const std::vector<Fault>& faults, int thread_id)
{
  for (int fault_i = 0; fault_i < faults.size(); ++fault_i) {
    Fault curr_fault = faults[fault_i];
    FaultType curr_ftype = curr_fault.faultType();
    int curr_fgid = curr_fault.gid();
    int curr_fpin = curr_fault.pinId();
    OclGate added_gate, added_pred;
    Value tie_val, other_tie_val, parallel_X;
    uint64_t mask = (uint64_t) 0x1 << (uint64_t) fault_i;
    parallel_X.setParallelX();

    // set gate type of added gate
    if (curr_ftype == SA1) {
      added_gate.gtype = T_OR;
      tie_val.setParallel0();
      other_tie_val.setParallel0();
      tie_val.bit0(tie_val.bit0() | mask);
      tie_val.bit1(tie_val.bit1() | mask);
    } else {
      added_gate.gtype = T_AND;
      tie_val.setParallel1();
      other_tie_val.setParallel1();
      mask = mask ^ ALL_ONES_64;
      tie_val.bit0(tie_val.bit0() & mask);
      tie_val.bit1(tie_val.bit1() & mask);
    }
    added_pred.gtype = T_INPUT;

    if (curr_fpin == 0) {  // output pin
      // set parameter of the added gate
      added_gate.level = levelThread(curr_fgid, thread_id);
      added_gate.numPreds = 2;  // the faulty gate and the added pred
      added_gate.numSuccs = numSuccsThread(curr_fgid, thread_id);
      added_gate.offsetPreds = _threadCurrPredsVecSizePtr[thread_id];
      added_gate.offsetSuccs = _threadCurrSuccsVecSizePtr[thread_id];

      // build preds of the added gate
      _predsPPtr[thread_id][added_gate.offsetPreds] = curr_fgid;
      _predsPPtr[thread_id][added_gate.offsetPreds + 1] = _threadModifiedNumNodesPtr[thread_id] + 1;  // the added pred

      // build succs of the added gate, and update preds of succs
      for (int succ_i = 0; succ_i < added_gate.numSuccs; ++succ_i) {
        int succ_gid = succsThread(curr_fgid, thread_id)[succ_i];
        _succsPPtr[thread_id][added_gate.offsetSuccs + succ_i] = succ_gid;
        // update pred of succs
        for (int pred_succ_i = 0; pred_succ_i < numPredsThread(succ_gid, thread_id); ++pred_succ_i) {
          int pred_succ_gid = predsThread(succ_gid, thread_id)[pred_succ_i];
          if (curr_fgid == pred_succ_gid) {
            predsThread(succ_gid, thread_id)[pred_succ_i] = _threadModifiedNumNodesPtr[thread_id];  // the added gate
            break;
          }
        }
      }

      // update succs of the faulty gate
      _gatesPPtr[thread_id][curr_fgid].numSuccs = 1;
      succsThread(curr_fgid, thread_id)[0] = _threadModifiedNumNodesPtr[thread_id];  // the added gate

      // set gmv of the added pred to tie val
      int added_pred_gid = _threadModifiedNumNodesPtr[thread_id] + 1;
      _gmvPPtr[thread_id][added_pred_gid] = tie_val;
      // added gate as initial event
      _eventManager->insertThreadEvent(_threadModifiedNumNodesPtr[thread_id], added_gate.level, thread_id);

      // add the added gate and added pred, update relevant parameters
      _gatesPPtr[thread_id][_threadModifiedNumNodesPtr[thread_id]] = added_gate;
      _gatesPPtr[thread_id][_threadModifiedNumNodesPtr[thread_id] + 1] = added_pred;
      _threadModifiedNumNodesPtr[thread_id] += 2;
      _threadCurrPredsVecSizePtr[thread_id] += 2;
      _threadCurrSuccsVecSizePtr[thread_id] += added_gate.numSuccs;

    } else {  // input pin
      int ori_pred_gid = predsThread(curr_fgid, thread_id)[curr_fpin - 1];

      // set parameter of the added gate
      added_gate.level = levelThread(ori_pred_gid, thread_id);
      added_gate.numPreds = 2;  // the ori pred gate and the added pred
      added_gate.numSuccs = 1;  // the faulty gate
      added_gate.offsetPreds = _threadCurrPredsVecSizePtr[thread_id];
      added_gate.offsetSuccs = _threadCurrSuccsVecSizePtr[thread_id];

      // build preds of the added gate
      _predsPPtr[thread_id][added_gate.offsetPreds] = ori_pred_gid;
      _predsPPtr[thread_id][added_gate.offsetPreds + 1] = _threadModifiedNumNodesPtr[thread_id] + 1;  // the added pred

      // update succs of the ori pred gate
      for (int succ_pred_i = 0; succ_pred_i < numSuccsThread(ori_pred_gid, thread_id); ++succ_pred_i) {
        if (curr_fgid == succsThread(ori_pred_gid, thread_id)[succ_pred_i]) {
          succsThread(ori_pred_gid, thread_id)[succ_pred_i] = _threadModifiedNumNodesPtr[thread_id];  // the added gate
          break;
        }
      }

      // build succs of the added gate
      _succsPPtr[thread_id][added_gate.offsetSuccs] = curr_fgid;

      // update preds of the faulty gate
      predsThread(curr_fgid, thread_id)[curr_fpin - 1] = _threadModifiedNumNodesPtr[thread_id];  // the added gate

      // set gmv of the added pred to tie val
      int added_pred_gid = _threadModifiedNumNodesPtr[thread_id] + 1;
      _gmvPPtr[thread_id][added_pred_gid] = tie_val;
      // added gate as initial event
      _eventManager->insertThreadEvent(_threadModifiedNumNodesPtr[thread_id], added_gate.level, thread_id);

      // add the added gate and added pred, update relevant parameters
      _gatesPPtr[thread_id][_threadModifiedNumNodesPtr[thread_id]] = added_gate;
      _gatesPPtr[thread_id][_threadModifiedNumNodesPtr[thread_id] + 1] = added_pred;
      _threadModifiedNumNodesPtr[thread_id] += 2;
      _threadCurrPredsVecSizePtr[thread_id] += 2;
      _threadCurrSuccsVecSizePtr[thread_id] += 1;
    }
  }
}

/**
 * @brief Inject parallel faults in vec mode.
 */
void Simulation::injectParallelFaultsByModifiedNetlistInVecMode(const std::vector<Fault>& faults, int thread_id)
{
  for (int fault_i = 0; fault_i < faults.size(); ++fault_i) {
    Fault curr_fault = faults[fault_i];
    FaultType curr_ftype = curr_fault.faultType();
    int curr_fgid = curr_fault.gid();
    int curr_fpin = curr_fault.pinId();
    OclGate added_gate, added_pred;
    Value256 tie_val, other_tie_val, parallel_X_256;
    uint64_t num_shift_bits = fault_i % 64;
    uint64_t mask = (uint64_t) 0x1 << num_shift_bits;
    uint64_t set_bit_val, other_bit_tie_val;
    parallel_X_256.set256ParallelX();

    // set gate type of added gate
    if (curr_ftype == SA1) {
      added_gate.gtype = T_OR;
      set_bit_val = ALL_ZEROS | mask;
      other_bit_tie_val = ALL_ZEROS;
      other_tie_val.set256Parallel0();
    } else {
      added_gate.gtype = T_AND;
      mask = mask ^ ALL_ONES_64;
      set_bit_val = ALL_ONES_64 & mask;
      other_bit_tie_val = ALL_ONES_64;
      other_tie_val.set256Parallel1();
    }
    tie_val.setOneBitVal(set_bit_val, other_bit_tie_val, fault_i);
    added_pred.gtype = T_INPUT;

    if (curr_fpin == 0) {  // output pin
      // set parameter of the added gate
      added_gate.level = levelThread(curr_fgid, thread_id);
      added_gate.numPreds = 2;  // the faulty gate and the added pred
      added_gate.numSuccs = numSuccsThread(curr_fgid, thread_id);
      added_gate.offsetPreds = _threadCurrPredsVecSizePtr[thread_id];
      added_gate.offsetSuccs = _threadCurrSuccsVecSizePtr[thread_id];

      // build preds of the added gate
      _predsPPtr[thread_id][added_gate.offsetPreds] = curr_fgid;
      _predsPPtr[thread_id][added_gate.offsetPreds + 1] = _threadModifiedNumNodesPtr[thread_id] + 1;  // the added pred

      // build succs of the added gate, and update preds of succs
      for (int succ_i = 0; succ_i < added_gate.numSuccs; ++succ_i) {
        int succ_gid = succsThread(curr_fgid, thread_id)[succ_i];
        _succsPPtr[thread_id][added_gate.offsetSuccs + succ_i] = succ_gid;
        // update pred of succs
        for (int pred_succ_i = 0; pred_succ_i < numPredsThread(succ_gid, thread_id); ++pred_succ_i) {
          int pred_succ_gid = predsThread(succ_gid, thread_id)[pred_succ_i];
          if (curr_fgid == pred_succ_gid) {
            predsThread(succ_gid, thread_id)[pred_succ_i] = _threadModifiedNumNodesPtr[thread_id];  // the added gate
            break;
          }
        }
      }

      // update succs of the faulty gate
      _gatesPPtr[thread_id][curr_fgid].numSuccs = 1;
      succsThread(curr_fgid, thread_id)[0] = _threadModifiedNumNodesPtr[thread_id];  // the added gate

      // set gmv of the added pred to tie val
      int added_pred_gid = _threadModifiedNumNodesPtr[thread_id] + 1;
      _gmv256PPtr[thread_id][added_pred_gid] = tie_val;
      // added gate as initial event
      _eventManager->insertThreadEvent(_threadModifiedNumNodesPtr[thread_id], added_gate.level, thread_id);

      // add the added gate and added pred, update relevant parameters
      _gatesPPtr[thread_id][_threadModifiedNumNodesPtr[thread_id]] = added_gate;
      _gatesPPtr[thread_id][_threadModifiedNumNodesPtr[thread_id] + 1] = added_pred;
      _threadModifiedNumNodesPtr[thread_id] += 2;
      _threadCurrPredsVecSizePtr[thread_id] += 2;
      _threadCurrSuccsVecSizePtr[thread_id] += added_gate.numSuccs;

    } else {  // input pin
      int ori_pred_gid = predsThread(curr_fgid, thread_id)[curr_fpin - 1];

      // set parameter of the added gate
      added_gate.level = levelThread(ori_pred_gid, thread_id);
      added_gate.numPreds = 2;  // the ori pred gate and the added pred
      added_gate.numSuccs = 1;  // the faulty gate
      added_gate.offsetPreds = _threadCurrPredsVecSizePtr[thread_id];
      added_gate.offsetSuccs = _threadCurrSuccsVecSizePtr[thread_id];

      // build preds of the added gate
      _predsPPtr[thread_id][added_gate.offsetPreds] = ori_pred_gid;
      _predsPPtr[thread_id][added_gate.offsetPreds + 1] = _threadModifiedNumNodesPtr[thread_id] + 1;  // the added pred

      // update succs of the ori pred gate
      for (int succ_pred_i = 0; succ_pred_i < numSuccsThread(ori_pred_gid, thread_id); ++succ_pred_i) {
        if (curr_fgid == succsThread(ori_pred_gid, thread_id)[succ_pred_i]) {
          succsThread(ori_pred_gid, thread_id)[succ_pred_i] = _threadModifiedNumNodesPtr[thread_id];  // the added gate
          break;
        }
      }

      // build succs of the added gate
      _succsPPtr[thread_id][added_gate.offsetSuccs] = curr_fgid;

      // update preds of the faulty gate
      predsThread(curr_fgid, thread_id)[curr_fpin - 1] = _threadModifiedNumNodesPtr[thread_id];  // the added gate

      // set gmv of the added pred to tie val
      int added_pred_gid = _threadModifiedNumNodesPtr[thread_id] + 1;
      _gmv256PPtr[thread_id][added_pred_gid] = tie_val;
      // added gate as initial event
      _eventManager->insertThreadEvent(_threadModifiedNumNodesPtr[thread_id], added_gate.level, thread_id);

      // add the added gate and added pred, update relevant parameters
      _gatesPPtr[thread_id][_threadModifiedNumNodesPtr[thread_id]] = added_gate;
      _gatesPPtr[thread_id][_threadModifiedNumNodesPtr[thread_id] + 1] = added_pred;
      _threadModifiedNumNodesPtr[thread_id] += 2;
      _threadCurrPredsVecSizePtr[thread_id] += 2;
      _threadCurrSuccsVecSizePtr[thread_id] += 1;
    }
  }
}

/**
 * @brief Remove parallel faults.
 */
void Simulation::removeParallelFaultsByRecoverNetlist(int thread_id)
{
  while (_threadModifiedNumNodesPtr[thread_id] > _numNodes) {
    int curr_added_gid = _threadModifiedNumNodesPtr[thread_id] - 2;
    OclGate added_gate = _gatesPPtr[thread_id][curr_added_gid];
    int ori_pred_gid;

    // restore gmv of the added gate
    _gmvPPtr[thread_id][curr_added_gid].setParallelX();

    // get the ori pred gid
    for (int pred_i = 0; pred_i < added_gate.numPreds; ++pred_i) {
      if (predsThread(curr_added_gid, thread_id)[pred_i] != _threadModifiedNumNodesPtr[thread_id]) {
        ori_pred_gid = predsThread(curr_added_gid, thread_id)[pred_i];
        break;
      }
    }

    if (numSuccsThread(curr_added_gid, thread_id) > 1) {
      // recover numSuccs of the ori pred gid
      _gatesPPtr[thread_id][ori_pred_gid].numSuccs = numSuccsThread(curr_added_gid, thread_id);
      // recover succs of the ori pred gid
      for (int succ_i = 0; succ_i < numSuccsThread(curr_added_gid, thread_id); ++succ_i) {
        int succ_gid = succsThread(curr_added_gid, thread_id)[succ_i];
        succsThread(ori_pred_gid, thread_id)[succ_i] = succ_gid;

        // recover preds of the ori succ gid
        for (int pred_succ_i = 0; pred_succ_i < numPredsThread(succ_gid, thread_id); ++pred_succ_i) {
          int pred_succ_gid = predsThread(succ_gid, thread_id)[pred_succ_i];
          if (curr_added_gid == pred_succ_gid) {
            predsThread(succ_gid, thread_id)[pred_succ_i] = ori_pred_gid;
            break;
          }
        }
      }

    } else {
      for (int succ_i = 0; succ_i < numSuccsThread(ori_pred_gid, thread_id); ++succ_i) {
        int succ_gid = succsThread(ori_pred_gid, thread_id)[succ_i];
        if (curr_added_gid == succ_gid) {
          int succ_succ_gid = succsThread(succ_gid, thread_id)[0];  // only one succ
          succsThread(ori_pred_gid, thread_id)[succ_i] = succ_succ_gid;
          for (int pred_succ_succ_i = 0; pred_succ_succ_i < numPredsThread(succ_succ_gid, thread_id); ++pred_succ_succ_i) {
            if (curr_added_gid == predsThread(succ_succ_gid, thread_id)[pred_succ_succ_i]) {
              predsThread(succ_succ_gid, thread_id)[pred_succ_succ_i] = ori_pred_gid;
              break;
            }
          }
          break;
        }
      }
    }
    _threadModifiedNumNodesPtr[thread_id] -= 2;
  }
  _threadCurrPredsVecSizePtr[thread_id] = _oriPredsVecSize;
  _threadCurrSuccsVecSizePtr[thread_id] = _oriSuccsVecSize;
}

/**
 * @brief Remove parallel faults in vec mode.
 */
void Simulation::removeParallelFaultsByRecoverNetlistInVecMode(int thread_id)
{
  Value256 parallel_X_256;
  parallel_X_256.set256ParallelX();

  while (_threadModifiedNumNodesPtr[thread_id] > _numNodes) {
    int curr_added_gid = _threadModifiedNumNodesPtr[thread_id] - 2;
    OclGate added_gate = _gatesPPtr[thread_id][curr_added_gid];
    int ori_pred_gid;

    // restore gmv of the added gate
    _gmv256PPtr[thread_id][curr_added_gid] = parallel_X_256;

    // get the ori pred gid
    for (int pred_i = 0; pred_i < added_gate.numPreds; ++pred_i) {
      if (predsThread(curr_added_gid, thread_id)[pred_i] != _threadModifiedNumNodesPtr[thread_id]) {
        ori_pred_gid = predsThread(curr_added_gid, thread_id)[pred_i];
        break;
      }
    }

    if (numSuccsThread(curr_added_gid, thread_id) > 1) {
      // recover numSuccs of the ori pred gid
      _gatesPPtr[thread_id][ori_pred_gid].numSuccs = numSuccsThread(curr_added_gid, thread_id);
      // recover succs of the ori pred gid
      for (int succ_i = 0; succ_i < numSuccsThread(curr_added_gid, thread_id); ++succ_i) {
        int succ_gid = succsThread(curr_added_gid, thread_id)[succ_i];
        succsThread(ori_pred_gid, thread_id)[succ_i] = succ_gid;

        // recover preds of the ori succ gid
        for (int pred_succ_i = 0; pred_succ_i < numPredsThread(succ_gid, thread_id); ++pred_succ_i) {
          int pred_succ_gid = predsThread(succ_gid, thread_id)[pred_succ_i];
          if (curr_added_gid == pred_succ_gid) {
            predsThread(succ_gid, thread_id)[pred_succ_i] = ori_pred_gid;
            break;
          }
        }
      }

    } else {
      for (int succ_i = 0; succ_i < numSuccsThread(ori_pred_gid, thread_id); ++succ_i) {
        int succ_gid = succsThread(ori_pred_gid, thread_id)[succ_i];
        if (curr_added_gid == succ_gid) {
          int succ_succ_gid = succsThread(succ_gid, thread_id)[0];  // only one succ
          succsThread(ori_pred_gid, thread_id)[succ_i] = succ_succ_gid;
          for (int pred_succ_succ_i = 0; pred_succ_succ_i < numPredsThread(succ_succ_gid, thread_id); ++pred_succ_succ_i) {
            if (curr_added_gid == predsThread(succ_succ_gid, thread_id)[pred_succ_succ_i]) {
              predsThread(succ_succ_gid, thread_id)[pred_succ_succ_i] = ori_pred_gid;
              break;
            }
          }
          break;
        }
      }
    }

    _threadModifiedNumNodesPtr[thread_id] -= 2;
  }
  _threadCurrPredsVecSizePtr[thread_id] = _oriPredsVecSize;
  _threadCurrSuccsVecSizePtr[thread_id] = _oriSuccsVecSize;
}

/**
 * @brief Compare parallel fault sim PO val to decide whewher any faults detected.
 */
void Simulation::compareParallelFsimPoVal(std::vector<FaultClass>& is_detected, int frame_id, int thread_id, int num_faults_in_group)
{
  for (int i = 1; i <= _needComparePoIdsPPtr[thread_id][0]; ++i) {
    int po_i = _needComparePoIdsPPtr[thread_id][i];
    _isPoNeedComparedPPtr[thread_id][po_i] = 0;
    int po_id = po_i + _firstPO;
    uint64_t gmv_val0 = _gmvPPtr[thread_id][po_id].bit0(), gmv_val1 = _gmvPPtr[thread_id][po_id].bit1();
    Value po_val;
    po_val.expandOneBitToValue(gmv_val0 & FIRST_BIT_64, gmv_val1 & FIRST_BIT_64);
    uint64_t po_val0 = po_val.bit0(), po_val1 = po_val.bit1();
    uint64_t diff_mask = (gmv_val0 ^ po_val0) | (gmv_val1 ^ po_val1);

    if (diff_mask) {
      uint64_t tmp_val0 = diff_mask & po_val0;  // skip po_val = X
      uint64_t tmp_val1 = diff_mask & (~po_val1);
      uint64_t is_diff = ~(tmp_val0 & tmp_val1) & diff_mask;
      if (is_diff) {  // judge DS or PT
        // check if gmv_val = X
        tmp_val0 = is_diff & gmv_val0;
        tmp_val1 = is_diff & (~gmv_val1);
        is_diff = ~(tmp_val0 & tmp_val1) & is_diff;
        // check if gmv_val = Z
        tmp_val0 = is_diff & (~gmv_val0);
        tmp_val1 = is_diff & gmv_val1;
        is_diff = ~(tmp_val0 & tmp_val1) & is_diff;
        for (int fault_i = 0; fault_i < num_faults_in_group; ++fault_i) {
          uint64_t curr_mask = (uint64_t) 0x1 << (uint64_t) fault_i;
          if ((is_detected[fault_i] != DS) && (diff_mask & curr_mask)) {
            FaultClass status = (is_diff & curr_mask) ? DS : PT;
            is_detected[fault_i] = status;
          }
        }
      }
    }
  }
  _needComparePoIdsPPtr[thread_id][0] = 0;  // clear
}

/**
 * @brief Compare parallel fault sim PO val to decide whewher any faults detected in vec mode.
 */
void Simulation::compareParallelFsimPoValInVecMode(std::vector<FaultClass>& is_detected, int frame_id, int num_faults_in_group,
                                                   int thread_id)
{
  for (int i = 1; i <= _needComparePoIdsPPtr[thread_id][0]; ++i) {
    int po_i = _needComparePoIdsPPtr[thread_id][i];
    _isPoNeedComparedPPtr[thread_id][po_i] = 0;
    int po_id = po_i + _firstPO;
    __m256i gmv_val0_256 = _gmv256PPtr[thread_id][po_id].bit0().int256, gmv_val1_256 = _gmv256PPtr[thread_id][po_id].bit1().int256;
    Value256 po_val_256;
    po_val_256.expandOneBitToValue256(_mm256_extract_epi64(gmv_val0_256, 3) & FIRST_BIT_64,
                                      _mm256_extract_epi64(gmv_val1_256, 3) & FIRST_BIT_64);
    __m256i po_val0_256 = po_val_256.bit0().int256, po_val1_256 = po_val_256.bit1().int256;
    __m256i diff_mask = _mm256_or_si256(_mm256_xor_si256(gmv_val0_256, po_val0_256), _mm256_xor_si256(gmv_val1_256, po_val1_256));

    // at least one bit is different
    if (_mm256_testz_si256(diff_mask, _allOnes256) == 0) {
      __m256i tmp_val0 = _mm256_and_si256(diff_mask, po_val0_256);  // skip po_val = X
      __m256i tmp_val1 = _mm256_and_si256(diff_mask, _mm256_xor_si256(po_val1_256, _allOnes256));
      __m256i is_diff = _mm256_and_si256(_mm256_xor_si256(_mm256_and_si256(tmp_val0, tmp_val1), _allOnes256), diff_mask);
      if (_mm256_testz_si256(is_diff, _allOnes256) == 0) {  // judge DS or PT
        // check if gmv_val = X
        tmp_val0 = _mm256_and_si256(is_diff, gmv_val0_256);
        tmp_val1 = _mm256_and_si256(is_diff, _mm256_xor_si256(gmv_val1_256, _allOnes256));
        is_diff = _mm256_and_si256(_mm256_xor_si256(_mm256_and_si256(tmp_val0, tmp_val1), _allOnes256), is_diff);
        // check if gmv_val = Z
        tmp_val0 = _mm256_and_si256(is_diff, _mm256_xor_si256(gmv_val0_256, _allOnes256));
        tmp_val1 = _mm256_and_si256(is_diff, gmv_val1_256);
        is_diff = _mm256_and_si256(_mm256_xor_si256(_mm256_and_si256(tmp_val0, tmp_val1), _allOnes256), is_diff);

        for (int fault_i = 0; fault_i < num_faults_in_group; ++fault_i) {
          uint64_t num_shift_bits = fault_i % 64;
          uint64_t mask = (uint64_t) 0x1 << num_shift_bits;
          __m256i curr_mask;
          if (fault_i < 64) {
            curr_mask = _mm256_set_epi64x(ALL_ZEROS, ALL_ZEROS, ALL_ZEROS, mask);
          } else if (fault_i < 128) {
            curr_mask = _mm256_set_epi64x(ALL_ZEROS, ALL_ZEROS, mask, ALL_ZEROS);
          } else if (fault_i < 192) {
            curr_mask = _mm256_set_epi64x(ALL_ZEROS, mask, ALL_ZEROS, ALL_ZEROS);
          } else {  // < 255
            curr_mask = _mm256_set_epi64x(mask, ALL_ZEROS, ALL_ZEROS, ALL_ZEROS);
          }
          if ((is_detected[fault_i] != DS) && (_mm256_testz_si256(_mm256_and_si256(diff_mask, curr_mask), _allOnes256) == 0)) {
            FaultClass status = (_mm256_testz_si256(_mm256_and_si256(is_diff, curr_mask), _allOnes256) == 0) ? DS : PT;
            is_detected[fault_i] = status;
          }
        }
      }
    }
  }
  _needComparePoIdsPPtr[thread_id][0] = 0;  // clear
}

/**
 * @brief Reset DFF to X.
 */
void Simulation::resetOriSimVal(int thread_id)
{
  Value parallel_X;
  parallel_X.setParallelX();

  // reset gmv of gid
  while (_changedGmvIdPPtr[thread_id][0] > 0) {
    int last_location = _changedGmvIdPPtr[thread_id][0];
    int gid = _changedGmvIdPPtr[thread_id][last_location];
    _gmvPPtr[thread_id][gid] = _storedGmvPPtr[thread_id][last_location];
    _changedGmvIdPPtr[thread_id][0]--;
    _isValChangedPPtr[thread_id][gid] = 0;
  }

  // reset gmv of dff master
  while (_masterChangedGmvIdPPtr[thread_id][0] > 0) {
    int master_id = _masterChangedGmvIdPPtr[thread_id][_masterChangedGmvIdPPtr[thread_id][0]];
    _masterChangedGmvIdPPtr[thread_id][0]--;
    _dffMasterValPPtr[thread_id][master_id] = parallel_X;
    _masterIsValChangedPPtr[thread_id][master_id] = 0;
  }
}

/**
 * @brief Reset DFF to X in vec mode.
 */
void Simulation::resetOriSimValInVecMode(int thread_id)
{
  Value256 parallel_X_256;
  parallel_X_256.set256ParallelX();

  // reset gmv of gid
  while (_changedGmvIdPPtr[thread_id][0] > 0) {
    int last_location = _changedGmvIdPPtr[thread_id][0];
    int gid = _changedGmvIdPPtr[thread_id][last_location];
    _gmv256PPtr[thread_id][gid] = _storedGmv256PPtr[thread_id][last_location];
    _changedGmvIdPPtr[thread_id][0]--;
    _isValChangedPPtr[thread_id][gid] = 0;
  }

  // reset gmv of dff master
  while (_masterChangedGmvIdPPtr[thread_id][0] > 0) {
    int master_id = _masterChangedGmvIdPPtr[thread_id][_masterChangedGmvIdPPtr[thread_id][0]];
    _masterChangedGmvIdPPtr[thread_id][0]--;
    _dffMasterVal256PPtr[thread_id][master_id] = parallel_X_256;
    _masterIsValChangedPPtr[thread_id][master_id] = 0;
  }
}

/**
 * @brief Update gate value and judge whether to add succ as event in different thread in fault sim.
 */
bool Simulation::updateThreadGateValueInFsim(int gid, int thread_id, Value new_val)
{
  if (_gmvPPtr[thread_id][gid] != new_val) {  // update curr thread val
    if (gid < _numNodes) {
      if (_isValChangedPPtr[thread_id][gid] == 0) {  // record ori val for later restore
        ++_changedGmvIdPPtr[thread_id][0];
        int new_location = _changedGmvIdPPtr[thread_id][0];
        _changedGmvIdPPtr[thread_id][new_location] = gid;
        _storedGmvPPtr[thread_id][new_location] = _gmvPPtr[thread_id][gid];
        _isValChangedPPtr[thread_id][gid] = 1;
      }
    }
    _gmvPPtr[thread_id][gid] = new_val;
    return true;
  }
  return false;
}

/**
 * @brief Update gate value and judge whether to add succ as event in different thread in fault sim.
 */
bool Simulation::updateThreadGateValue256InFsim(int gid, int thread_id, Value256 new_val)
{
  if (_gmv256PPtr[thread_id][gid] != new_val) {  // update curr thread val
    if (gid < _numNodes) {
      if (_isValChangedPPtr[thread_id][gid] == 0) {  // record ori val for later restore
        ++_changedGmvIdPPtr[thread_id][0];
        int new_location = _changedGmvIdPPtr[thread_id][0];
        _changedGmvIdPPtr[thread_id][new_location] = gid;
        _storedGmv256PPtr[thread_id][new_location] = _gmv256PPtr[thread_id][gid];
        _isValChangedPPtr[thread_id][gid] = 1;
      }
    }
    _gmv256PPtr[thread_id][gid] = new_val;
    return true;
  }
  return false;
}

/**
 * @brief Add successors events in different thread.
 */
void Simulation::addThreadSuccEvents(int gid, int thread_id)
{
  for (int succ_i = 0; succ_i < numSuccsThread(gid, thread_id); ++succ_i) {
    int succ_id = succsThread(gid, thread_id)[succ_i];
    _eventManager->insertThreadEvent(succ_id, levelThread(succ_id, thread_id), thread_id);
  }
}

/**
 * @brief Update gate value256 and judge whether to add succ as event.
 */
bool Simulation::updateThreadGateValue256(int gid, int thread_id, Value256 new_val)
{
  if (_gmv256PPtr[thread_id][gid] != new_val) {  // update curr time frame val
    _gmv256PPtr[thread_id][gid] = new_val;
    return true;
  }
  return false;
}

/**
 * @brief Eval gate in good sim.
 */
void Simulation::evalGateBasedOnCurrTimeFrame(int gid, uint64_t& val0, uint64_t& val1)
{
  switch (type(gid)) {
    case T_INPUT: {
      // gmv index is the same as id
      val0 = _gmvPtr[gid].bit0();
      val1 = _gmvPtr[gid].bit1();
      break;
    }
    case T_TIE1:  // (1,1)
    {
      val0 = ALL_ONES_64;
      val1 = ALL_ONES_64;
      break;
    }
    case T_TIE0:  // (0,0)
    {
      val0 = 0;
      val1 = 0;
      break;
    }
    case T_TIEX:  // (1,0)
    {
      val0 = ALL_ONES_64;
      val1 = 0;
      break;
    }
    case T_TIEZ:  // (0,1)
    {
      val0 = 0;
      val1 = ALL_ONES_64;
      break;
    }
    case T_OUTPUT:
    case T_BUF:
    case T_BUF_FB: {
      Value pred_val = _gmvPtr[preds(gid)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      break;
    }
    case T_BUFZ: {
      Value pred_val = _gmvPtr[preds(gid)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();

      uint64_t xz_mask = pred_val.bit0() ^ pred_val.bit1();
      // set bits in xz_mask to X (1, 0)
      val0 |= xz_mask;
      val1 &= (~xz_mask);
      break;
    }
    case T_AND: {
      Value pred_val = _gmvPtr[preds(gid)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPreds(gid); ++pred_i) {
        int pred_id = preds(gid)[pred_i];
        pred_val = _gmvPtr[pred_id];
        val0 &= pred_val.bit0();
        val1 &= pred_val.bit1();
      }
      break;
    }
    case T_NAND: {
      Value pred_val = _gmvPtr[preds(gid)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPreds(gid); ++pred_i) {
        int pred_id = preds(gid)[pred_i];
        pred_val = _gmvPtr[pred_id];
        val0 &= pred_val.bit0();
        val1 &= pred_val.bit1();
      }
      uint64_t tmp_val = val0;
      val0 = ALL_ONES_64 ^ val1;
      val1 = ALL_ONES_64 ^ tmp_val;
      break;
    }
    case T_OR: {
      Value pred_val = _gmvPtr[preds(gid)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPreds(gid); ++pred_i) {
        int pred_id = preds(gid)[pred_i];
        pred_val = _gmvPtr[pred_id];
        val0 |= pred_val.bit0();
        val1 |= pred_val.bit1();
      }
      break;
    }
    case T_NOR: {
      Value pred_val = _gmvPtr[preds(gid)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPreds(gid); ++pred_i) {
        int pred_id = preds(gid)[pred_i];
        pred_val = _gmvPtr[pred_id];
        val0 |= pred_val.bit0();
        val1 |= pred_val.bit1();
      }
      uint64_t tmp_val = val0;
      val0 = ALL_ONES_64 ^ val1;
      val1 = ALL_ONES_64 ^ tmp_val;
      break;
    }
    case T_NOT: {
      Value pred_val = _gmvPtr[preds(gid)[0]];
      val0 = pred_val.bit1() ^ ALL_ONES_64;
      val1 = pred_val.bit0() ^ ALL_ONES_64;
      break;
    }
    case T_XOR: {
      Value pred_val = _gmvPtr[preds(gid)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPreds(gid); ++pred_i) {
        int pred_id = preds(gid)[pred_i];
        pred_val = _gmvPtr[pred_id];
        uint64_t v1 = ALL_ONES_64 ^ (((ALL_ONES_64 ^ pred_val.bit0()) & (ALL_ONES_64 ^ val0)) | (pred_val.bit1() & val1));
        val1 = ((ALL_ONES_64 ^ pred_val.bit0()) & val1) | (pred_val.bit1() & (ALL_ONES_64 ^ val0));
        val0 = v1;
      }
      break;
    }
    case T_XNOR: {
      Value pred_val = _gmvPtr[preds(gid)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPreds(gid); ++pred_i) {
        int pred_id = preds(gid)[pred_i];
        pred_val = _gmvPtr[pred_id];
        uint64_t v1 = ALL_ONES_64 ^ (((ALL_ONES_64 ^ pred_val.bit0()) & (ALL_ONES_64 ^ val0)) | (pred_val.bit1() & val1));
        val1 = ((ALL_ONES_64 ^ pred_val.bit0()) & val1) | (pred_val.bit1() & (ALL_ONES_64 ^ val0));
        val0 = v1;
      }
      uint64_t tmp_val = val0;
      val0 = ALL_ONES_64 ^ val1;
      val1 = ALL_ONES_64 ^ tmp_val;
      break;
    }
    case T_BUS: {  // keep Z output as is
      val0 = 0;
      val1 = ALL_ONES_64;
      for (int pred_i = 0; pred_i < numPreds(gid); ++pred_i) {
        int pred_id = preds(gid)[pred_i];
        Value pred_val = _gmvPtr[pred_id];
        val0 |= pred_val.bit0();
        val1 &= pred_val.bit1();
      }
      break;
    }
    case T_BUS_GOHIGH: {  // adjust Z output to 1 in the end
      val0 = 0;
      val1 = ALL_ONES_64;
      for (int pred_i = 0; pred_i < numPreds(gid); ++pred_i) {
        int pred_id = preds(gid)[pred_i];
        Value pred_val = _gmvPtr[pred_id];
        val0 |= pred_val.bit0();
        val1 &= pred_val.bit1();
      }
      // Changing Z bit to 1
      uint64_t z_mask = (~val0) & val1;  // Get bits with Z (0,1) values
      val0 |= z_mask;
      val1 |= z_mask;
      break;
    }
    case T_BUS_GOLOW: {  // adjust Z output to 0 in the end
      val0 = 0;
      val1 = ALL_ONES_64;
      for (int pred_i = 0; pred_i < numPreds(gid); ++pred_i) {
        int pred_id = preds(gid)[pred_i];
        Value pred_val = _gmvPtr[pred_id];
        val0 |= pred_val.bit0();
        val1 &= pred_val.bit1();
      }
      // Changing Z bit to 0
      uint64_t z_mask_bar = ~((~val0) & val1);  // Get bits with Z:(0,1)
      val0 &= z_mask_bar;
      val1 &= z_mask_bar;
      break;
    }
    case T_BUS_X: {  // adjust Z output to X in the end
      val0 = 0;
      val1 = ALL_ONES_64;
      for (int pred_i = 0; pred_i < numPreds(gid); ++pred_i) {
        int pred_id = preds(gid)[pred_i];
        Value pred_val = _gmvPtr[pred_id];
        val0 |= pred_val.bit0();
        val1 &= pred_val.bit1();
      }
      // Changing Z bit to X
      uint64_t z_mask = (~val0) & val1;  // Get bits with Z (0,1) values
      val0 |= z_mask;
      val1 &= (~z_mask);
      break;
    }
    case T_TRISTATEBUF: {
      val0 = ALL_ONES_64;
      val1 = 0;
      Value en_val = _gmvPtr[preds(gid)[0]];
      Value i_val = _gmvPtr[preds(gid)[1]];
      // e1: enable is 1; D passthrough
      uint64_t e1_mask = en_val.bit0() & en_val.bit1();
      val0 = (val0 & ~e1_mask) | (i_val.bit0() & e1_mask);
      val1 = (val1 & ~e1_mask) | (i_val.bit1() & e1_mask);
      // e0: enable 0; output Z (0,1)
      uint64_t e0_mask = ~(en_val.bit0() | en_val.bit1());
      val0 &= ~e0_mask;  // output Z (0,1) with enable 0
      val1 |= e0_mask;
      break;
    }
    case T_TRISTATEINV: {
      val0 = ALL_ONES_64;
      val1 = 0;
      Value en_val = _gmvPtr[preds(gid)[0]];
      Value i_val = _gmvPtr[preds(gid)[1]];
      // e1: enable is 1; D inverted
      uint64_t e1_mask = en_val.bit0() & en_val.bit1();
      val0 = (val0 & ~e1_mask) | ((i_val.bit1() ^ ALL_ONES_64) & e1_mask);
      val1 = (val1 & ~e1_mask) | ((i_val.bit0() ^ ALL_ONES_64) & e1_mask);
      // e0: enable 0; output Z (0,1)
      uint64_t e0_mask = ~(en_val.bit0() | en_val.bit1());
      val0 &= ~e0_mask;  // output Z (0,1) with enable 0
      val1 |= e0_mask;
      break;
    }
    case T_MUX_2:  // Assume port (sel, d0, d1)
    {
      // s1: selector is 1; s0: selector is 0; dq: d0 == d1
      // start with Xs (1,0)
      val0 = ALL_ONES_64;
      val1 = 0;

      Value val_sel = _gmvPtr[preds(gid)[0]];
      uint64_t val0_sel = val_sel.bit0();
      uint64_t val1_sel = val_sel.bit1();

      // assign d0 to output if sel is all 0's
      uint64_t s0_mask = ~(val0_sel | val1_sel);
      Value val_d0 = _gmvPtr[preds(gid)[1]];
      if (ALL_ONES_64 == s0_mask) {
        val0 = val_d0.bit0();
        val1 = val_d0.bit1();
        break;
      }
      // assign d1 to output if sel is all 1's
      uint64_t s1_mask = val0_sel & val1_sel;
      Value val_d1 = _gmvPtr[preds(gid)[2]];
      if (ALL_ONES_64 == s1_mask) {
        val0 = val_d1.bit0();
        val1 = val_d1.bit1();
        break;
      }

      uint64_t val0_d0 = val_d0.bit0();
      uint64_t val1_d0 = val_d0.bit1();
      uint64_t val0_d1 = val_d1.bit0();
      uint64_t val1_d1 = val_d1.bit1();
      // pass d0 for sel==0 and d0==d1
      // dq_mask stands for bit mask where d0 and d1 are equal
      uint64_t dq_mask = ~((val0_d0 ^ val0_d1) | (val1_d0 ^ val1_d1));
      uint64_t s0_dq_mask = s0_mask | dq_mask;
      val0 &= ((val0_d0 & s0_dq_mask) | ~s0_dq_mask);
      val1 |= (val1_d0 & s0_dq_mask);

      // pass d1 for sel==1
      val0 &= ((val0_d1 & s1_mask) | ~s1_mask);
      val1 |= (val1_d1 & s1_mask);

      break;
    }
    /* assume port set, reset, clock and data
    /    DFF primitive assumes master-slave flop structure.
    /    Signal 'set', 'reset' are asynchronous and affect the gmvs of
    /      master & slave D-latches immediately.
    /    gmvIndex(DFF) stores the gmv of the slave D-latch which is used
    /      to drive its fanout gates.
    /    gmvIndex(DFF) + 1 stores the gmv of the master D-latch that is
    /      used to drive the slave D-latch.
    */
    case T_DFF_PRI: {
      int master_gid = gid - _firstDffId;

      uint64_t set_val0 = _gmvPtr[preds(gid)[0]].bit0();
      uint64_t set_val1 = _gmvPtr[preds(gid)[0]].bit1();
      uint64_t rst_val0 = _gmvPtr[preds(gid)[1]].bit0();
      uint64_t rst_val1 = _gmvPtr[preds(gid)[1]].bit1();
      uint64_t clk_val0 = _gmvPtr[preds(gid)[2]].bit0();
      uint64_t clk_val1 = _gmvPtr[preds(gid)[2]].bit1();
      Value dat_val = _gmvPtr[preds(gid)[3]];

      uint64_t set1_mask = set_val0 & set_val1;
      uint64_t setx_mask = set_val0 & (~set_val1);
      uint64_t rst1_mask = rst_val0 & rst_val1;
      uint64_t rstx_mask = rst_val0 & (~rst_val1);
      uint64_t clk1_mask = clk_val0 & clk_val1;
      uint64_t clk0_mask = ~(clk_val0 | clk_val1);
      uint64_t clkx_mask = clk_val0 & (~clk_val1);

      if (set1_mask & rst1_mask) {
        LOG_ERROR << "conflicting set and reset values for DFF (" << gid + 1 << ")\n"
                  << mask64ToBinary(set1_mask) << " set1\n"
                  << mask64ToBinary(rst1_mask) << " rst1" << std::endl;
        break;
      }

      // init val0 & val1 as slave gmv
      val0 = _gmvPtr[gid].bit0();
      val1 = _gmvPtr[gid].bit1();

      // when clock==0, din-->master and slave holds its state.
      if (clk0_mask) {
        uint64_t dat_v0 = dat_val.bit0();
        uint64_t dat_v1 = dat_val.bit1();
        uint64_t mas_v0 = _dffMasterValPtr[master_gid].bit0();
        uint64_t mas_v1 = _dffMasterValPtr[master_gid].bit1();
        _dffMasterValPtr[master_gid].bit0((mas_v0 & ~clk0_mask) | (dat_v0 & clk0_mask));
        _dffMasterValPtr[master_gid].bit1((mas_v1 & ~clk0_mask) | (dat_v1 & clk0_mask));
      }
      // when clock==1, master holds its state and master-->slave.
      if (clk1_mask) {
        uint64_t mas_v0 = _dffMasterValPtr[master_gid].bit0();
        uint64_t mas_v1 = _dffMasterValPtr[master_gid].bit1();
        uint64_t sla_v0 = _gmvPtr[gid].bit0();
        uint64_t sla_v1 = _gmvPtr[gid].bit1();
        _dffMasterValPtr[master_gid] = _dffMasterValPtr[master_gid];
        val0 = (sla_v0 & ~clk1_mask) | (mas_v0 & clk1_mask);
        val1 = (sla_v1 & ~clk1_mask) | (mas_v1 & clk1_mask);
      }
      // when clock==x, if din==slave, then din-->master, else, then set slave and master to X.
      if (clkx_mask) {
        uint64_t dat_v0 = dat_val.bit0();
        uint64_t dat_v1 = dat_val.bit1();
        uint64_t mas_v0 = _dffMasterValPtr[master_gid].bit0();
        uint64_t mas_v1 = _dffMasterValPtr[master_gid].bit1();
        uint64_t sla_v0 = _gmvPtr[gid].bit0();
        uint64_t sla_v1 = _gmvPtr[gid].bit1();
        uint64_t eq_mask = (~(dat_v0 ^ sla_v0)) & (~(dat_v1 ^ sla_v1)) & clkx_mask;
        uint64_t neq_mask = ((dat_v0 ^ sla_v0) | (dat_v1 ^ sla_v1)) & clkx_mask;
        // din-->master
        uint64_t new_mas_v0 = (mas_v0 & ~eq_mask) | (dat_v0 & eq_mask);
        uint64_t new_mas_v1 = (mas_v1 & ~eq_mask) | (dat_v1 & eq_mask);
        // set master and slave to X
        new_mas_v0 = new_mas_v0 | neq_mask;
        new_mas_v1 = new_mas_v1 & ~(neq_mask);
        _dffMasterValPtr[master_gid].bit0(new_mas_v0);
        _dffMasterValPtr[master_gid].bit1(new_mas_v1);
        val0 = val0 | neq_mask;
        val1 = val1 & (~neq_mask);
      }

      // adjust bit values for enable set and enable reset
      if (set1_mask) {
        uint64_t mas_v0 = _dffMasterValPtr[master_gid].bit0();
        uint64_t mas_v1 = _dffMasterValPtr[master_gid].bit1();
        _dffMasterValPtr[master_gid].bit0(mas_v0 | set1_mask);
        _dffMasterValPtr[master_gid].bit1(mas_v1 | set1_mask);
        val0 |= set1_mask;
        val1 |= set1_mask;
      }
      if (rst1_mask) {
        uint64_t mas_v0 = _dffMasterValPtr[master_gid].bit0();
        uint64_t mas_v1 = _dffMasterValPtr[master_gid].bit1();
        _dffMasterValPtr[master_gid].bit0(mas_v0 & (~rst1_mask));
        _dffMasterValPtr[master_gid].bit1(mas_v1 & (~rst1_mask));
        val0 &= ~rst1_mask;
        val1 &= ~rst1_mask;
      }

      // adjust bit values for unknown set and unknown reset
      if (setx_mask) {
        uint64_t mas_v0 = _dffMasterValPtr[master_gid].bit0();
        uint64_t mas_v1 = _dffMasterValPtr[master_gid].bit1();
        uint64_t mas1_mask = mas_v0 & mas_v1;
        uint64_t masx_mask = setx_mask & (~mas1_mask);  // keep val1
        _dffMasterValPtr[master_gid].bit0(mas_v0 | masx_mask);
        _dffMasterValPtr[master_gid].bit1(mas_v1 & (~masx_mask));

        uint64_t val1_mask = val0 & val1;
        uint64_t x_mask = setx_mask & (~val1_mask);  // keep val1
        val0 = val0 | x_mask;
        val1 = val1 & (~x_mask);
      }
      if (rstx_mask) {
        uint64_t mas_v0 = _dffMasterValPtr[master_gid].bit0();
        uint64_t mas_v1 = _dffMasterValPtr[master_gid].bit1();
        uint64_t mas0_mask = ~(mas_v0 | mas_v1);
        uint64_t masx_mask = rstx_mask & (~mas0_mask);  // keep val0
        _dffMasterValPtr[master_gid].bit0(mas_v0 | masx_mask);
        _dffMasterValPtr[master_gid].bit1(mas_v1 & (~masx_mask));

        uint64_t val0_mask = ~(val0 | val1);
        uint64_t x_mask = rstx_mask & (~val0_mask);  // keep val0
        val0 = val0 | x_mask;
        val1 = val1 & (~x_mask);
      }
      break;
    }
    case T_DLA_PRI:  // assume port set, reset, clock and data
    {
      uint64_t set_val0 = _gmvPtr[preds(gid)[0]].bit0();
      uint64_t set_val1 = _gmvPtr[preds(gid)[0]].bit1();
      uint64_t rst_val0 = _gmvPtr[preds(gid)[1]].bit0();
      uint64_t rst_val1 = _gmvPtr[preds(gid)[1]].bit1();
      uint64_t clk_val0 = _gmvPtr[preds(gid)[2]].bit0();
      uint64_t clk_val1 = _gmvPtr[preds(gid)[2]].bit1();
      Value dat_val = _gmvPtr[preds(gid)[3]];

      uint64_t set1_mask = set_val0 & set_val1;
      uint64_t rst1_mask = rst_val0 & rst_val1;
      uint64_t clk1_mask = clk_val0 & clk_val1;
      uint64_t clk0_mask = ~(clk_val0 | clk_val1);
      uint64_t x_mask = (set_val0 & (~set_val1)) | (rst_val0 & (~rst_val1)) | (clk_val0 & (~clk_val1));

      if (set1_mask & rst1_mask) {
        LOG_ERROR << "conflicting set and reset values for D latch (" << gid + 1 << ")\n"
                  << mask64ToBinary(set1_mask) << " set1\n"
                  << mask64ToBinary(rst1_mask) << " rst1" << std::endl;
        break;
      }

      uint64_t in0 = dat_val.bit0();
      uint64_t in1 = dat_val.bit1();
      val0 = _gmvPtr[gid].bit0();
      val1 = _gmvPtr[gid].bit1();

      // pass through data with clk1_mask
      val0 = (val0 & (~clk1_mask)) | (in0 & clk1_mask);
      val1 = (val1 & (~clk1_mask)) | (in1 & clk1_mask);

      // adjust bit values for set and reset
      if (set1_mask) {
        val0 |= set1_mask;
        val1 |= set1_mask;
      }
      if (rst1_mask) {
        val0 &= (~rst1_mask);
        val1 &= (~rst1_mask);
      }

      // set output as X-(1,0) with clkx_mask
      // remaining bits of ck0_mask are kept
      // also set output as X with setx_mask and rstx_mask
      if (x_mask) {
        val0 = val0 | x_mask;
        val1 = val1 & ~(x_mask);
      }
      break;
    }
    default: {
      LOG_ERROR << "gate type " << LUT_GTypeToName[type(gid)] << " is not supported.";
      break;
    }
  }
}

/**
 * @brief Eval gate in fault sim.
 */
void Simulation::evalGateForThreadFsim(int gid, int thread_id, int num_faults_in_group, uint64_t& val0, uint64_t& val1)
{
  switch (typeThread(gid, thread_id)) {
    case T_INPUT: {
      // gmv index is the same as id
      val0 = _gmvPPtr[thread_id][gid].bit0();
      val1 = _gmvPPtr[thread_id][gid].bit1();
      break;
    }
    case T_TIE1:  // (1,1)
    {
      val0 = ALL_ONES_64;
      val1 = ALL_ONES_64;
      break;
    }
    case T_TIE0:  // (0,0)
    {
      val0 = 0;
      val1 = 0;
      break;
    }
    case T_TIEX:  // (1,0)
    {
      val0 = ALL_ONES_64;
      val1 = 0;
      break;
    }
    case T_TIEZ:  // (0,1)
    {
      val0 = 0;
      val1 = ALL_ONES_64;
      break;
    }
    case T_OUTPUT:
    case T_BUF:
    case T_BUF_FB: {
      Value pred_val = _gmvPPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      break;
    }
    case T_BUFZ: {
      Value pred_val = _gmvPPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();

      uint64_t xz_mask = pred_val.bit0() ^ pred_val.bit1();
      // set bits in xz_mask to X (1, 0)
      val0 |= xz_mask;
      val1 &= (~xz_mask);
      break;
    }
    case T_AND: {
      Value pred_val = _gmvPPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        pred_val = _gmvPPtr[thread_id][pred_id];
        val0 &= pred_val.bit0();
        val1 &= pred_val.bit1();
      }
      break;
    }
    case T_NAND: {
      Value pred_val = _gmvPPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        pred_val = _gmvPPtr[thread_id][pred_id];
        val0 &= pred_val.bit0();
        val1 &= pred_val.bit1();
      }
      uint64_t tmp_val = val0;
      val0 = ALL_ONES_64 ^ val1;
      val1 = ALL_ONES_64 ^ tmp_val;
      break;
    }
    case T_OR: {
      Value pred_val = _gmvPPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        pred_val = _gmvPPtr[thread_id][pred_id];
        val0 |= pred_val.bit0();
        val1 |= pred_val.bit1();
      }
      break;
    }
    case T_NOR: {
      Value pred_val = _gmvPPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        pred_val = _gmvPPtr[thread_id][pred_id];
        val0 |= pred_val.bit0();
        val1 |= pred_val.bit1();
      }
      uint64_t tmp_val = val0;
      val0 = ALL_ONES_64 ^ val1;
      val1 = ALL_ONES_64 ^ tmp_val;
      break;
    }
    case T_NOT: {
      Value pred_val = _gmvPPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit1() ^ ALL_ONES_64;
      val1 = pred_val.bit0() ^ ALL_ONES_64;
      break;
    }
    case T_XOR: {
      Value pred_val = _gmvPPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        pred_val = _gmvPPtr[thread_id][pred_id];
        uint64_t v1 = ALL_ONES_64 ^ (((ALL_ONES_64 ^ pred_val.bit0()) & (ALL_ONES_64 ^ val0)) | (pred_val.bit1() & val1));
        val1 = ((ALL_ONES_64 ^ pred_val.bit0()) & val1) | (pred_val.bit1() & (ALL_ONES_64 ^ val0));
        val0 = v1;
      }
      break;
    }
    case T_XNOR: {
      Value pred_val = _gmvPPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        pred_val = _gmvPPtr[thread_id][pred_id];
        uint64_t v1 = ALL_ONES_64 ^ (((ALL_ONES_64 ^ pred_val.bit0()) & (ALL_ONES_64 ^ val0)) | (pred_val.bit1() & val1));
        val1 = ((ALL_ONES_64 ^ pred_val.bit0()) & val1) | (pred_val.bit1() & (ALL_ONES_64 ^ val0));
        val0 = v1;
      }
      uint64_t tmp_val = val0;
      val0 = ALL_ONES_64 ^ val1;
      val1 = ALL_ONES_64 ^ tmp_val;
      break;
    }
    case T_BUS: {  // keep Z output as is
      val0 = 0;
      val1 = ALL_ONES_64;
      for (int pred_i = 0; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        Value pred_val = _gmvPPtr[thread_id][pred_id];
        val0 |= pred_val.bit0();
        val1 &= pred_val.bit1();
      }
      break;
    }
    case T_BUS_GOHIGH: {  // adjust Z output to 1 in the end
      val0 = 0;
      val1 = ALL_ONES_64;
      for (int pred_i = 0; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        Value pred_val = _gmvPPtr[thread_id][pred_id];
        val0 |= pred_val.bit0();
        val1 &= pred_val.bit1();
      }
      // Changing Z bit to 1
      uint64_t z_mask = (~val0) & val1;  // Get bits with Z (0,1) values
      val0 |= z_mask;
      val1 |= z_mask;
      break;
    }
    case T_BUS_GOLOW: {  // adjust Z output to 0 in the end
      val0 = 0;
      val1 = ALL_ONES_64;
      for (int pred_i = 0; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        Value pred_val = _gmvPPtr[thread_id][pred_id];
        val0 |= pred_val.bit0();
        val1 &= pred_val.bit1();
      }
      // Changing Z bit to 0
      uint64_t z_mask_bar = ~((~val0) & val1);  // Get bits with Z:(0,1)
      val0 &= z_mask_bar;
      val1 &= z_mask_bar;
      break;
    }
    case T_BUS_X: {  // adjust Z output to X in the end
      val0 = 0;
      val1 = ALL_ONES_64;
      for (int pred_i = 0; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        Value pred_val = _gmvPPtr[thread_id][pred_id];
        val0 |= pred_val.bit0();
        val1 &= pred_val.bit1();
      }
      // Changing Z bit to X
      uint64_t z_mask = (~val0) & val1;  // Get bits with Z (0,1) values
      val0 |= z_mask;
      val1 &= (~z_mask);
      break;
    }
    case T_TRISTATEBUF: {
      val0 = ALL_ONES_64;
      val1 = 0;
      Value en_val = _gmvPPtr[thread_id][predsThread(gid, thread_id)[0]];
      Value i_val = _gmvPPtr[thread_id][predsThread(gid, thread_id)[1]];
      // e1: enable is 1; D passthrough
      uint64_t e1_mask = en_val.bit0() & en_val.bit1();
      val0 = (val0 & ~e1_mask) | (i_val.bit0() & e1_mask);
      val1 = (val1 & ~e1_mask) | (i_val.bit1() & e1_mask);
      // e0: enable 0; output Z (0,1)
      uint64_t e0_mask = ~(en_val.bit0() | en_val.bit1());
      val0 &= ~e0_mask;  // output Z (0,1) with enable 0
      val1 |= e0_mask;
      break;
    }
    case T_TRISTATEINV: {
      val0 = ALL_ONES_64;
      val1 = 0;
      Value en_val = _gmvPPtr[thread_id][predsThread(gid, thread_id)[0]];
      Value i_val = _gmvPPtr[thread_id][predsThread(gid, thread_id)[1]];
      // e1: enable is 1; D inverted
      uint64_t e1_mask = en_val.bit0() & en_val.bit1();
      val0 = (val0 & ~e1_mask) | ((i_val.bit1() ^ ALL_ONES_64) & e1_mask);
      val1 = (val1 & ~e1_mask) | ((i_val.bit0() ^ ALL_ONES_64) & e1_mask);
      // e0: enable 0; output Z (0,1)
      uint64_t e0_mask = ~(en_val.bit0() | en_val.bit1());
      val0 &= ~e0_mask;  // output Z (0,1) with enable 0
      val1 |= e0_mask;
      break;
    }
    case T_MUX_2:  // Assume port (sel, d0, d1)
    {
      // s1: selector is 1; s0: selector is 0; dq: d0 == d1
      // start with Xs (1,0)
      val0 = ALL_ONES_64;
      val1 = 0;

      Value val_sel = _gmvPPtr[thread_id][predsThread(gid, thread_id)[0]];
      uint64_t val0_sel = val_sel.bit0();
      uint64_t val1_sel = val_sel.bit1();

      // assign d0 to output if sel is all 0's
      uint64_t s0_mask = ~(val0_sel | val1_sel);
      Value val_d0 = _gmvPPtr[thread_id][predsThread(gid, thread_id)[1]];
      if (ALL_ONES_64 == s0_mask) {
        val0 = val_d0.bit0();
        val1 = val_d0.bit1();
        break;
      }
      // assign d1 to output if sel is all 1's
      uint64_t s1_mask = val0_sel & val1_sel;
      Value val_d1 = _gmvPPtr[thread_id][predsThread(gid, thread_id)[2]];
      if (ALL_ONES_64 == s1_mask) {
        val0 = val_d1.bit0();
        val1 = val_d1.bit1();
        break;
      }

      uint64_t val0_d0 = val_d0.bit0();
      uint64_t val1_d0 = val_d0.bit1();
      uint64_t val0_d1 = val_d1.bit0();
      uint64_t val1_d1 = val_d1.bit1();
      // pass d0 for sel==0 and d0==d1
      // dq_mask stands for bit mask where d0 and d1 are equal
      uint64_t dq_mask = ~((val0_d0 ^ val0_d1) | (val1_d0 ^ val1_d1));
      uint64_t s0_dq_mask = s0_mask | dq_mask;
      val0 &= ((val0_d0 & s0_dq_mask) | ~s0_dq_mask);
      val1 |= (val1_d0 & s0_dq_mask);

      // pass d1 for sel==1
      val0 &= ((val0_d1 & s1_mask) | ~s1_mask);
      val1 |= (val1_d1 & s1_mask);

      break;
    }
    /* assume port set, reset, clock and data
    /    DFF primitive assumes master-slave flop structure.
    /    Signal 'set', 'reset' are asynchronous and affect the gmvs of
    /      master & slave D-latches immediately.
    /    gmvIndex(DFF) stores the gmv of the slave D-latch which is used
    /      to drive its fanout gates.
    /    gmvIndex(DFF) + 1 stores the gmv of the master D-latch that is
    /      used to drive the slave D-latch.
    */
    case T_DFF_PRI: {
      uint64_t set_val0 = _gmvPPtr[thread_id][predsThread(gid, thread_id)[0]].bit0();
      uint64_t set_val1 = _gmvPPtr[thread_id][predsThread(gid, thread_id)[0]].bit1();
      uint64_t rst_val0 = _gmvPPtr[thread_id][predsThread(gid, thread_id)[1]].bit0();
      uint64_t rst_val1 = _gmvPPtr[thread_id][predsThread(gid, thread_id)[1]].bit1();
      uint64_t clk_val0 = _gmvPPtr[thread_id][predsThread(gid, thread_id)[2]].bit0();
      uint64_t clk_val1 = _gmvPPtr[thread_id][predsThread(gid, thread_id)[2]].bit1();
      Value dat_val = _gmvPPtr[thread_id][predsThread(gid, thread_id)[3]];

      uint64_t set1_mask = set_val0 & set_val1;
      uint64_t setx_mask = set_val0 & (~set_val1);
      uint64_t rst1_mask = rst_val0 & rst_val1;
      uint64_t rstx_mask = rst_val0 & (~rst_val1);
      uint64_t clk1_mask = clk_val0 & clk_val1;
      uint64_t clk0_mask = ~(clk_val0 | clk_val1);
      uint64_t clkx_mask = clk_val0 & (~clk_val1);

      if (set1_mask & rst1_mask) {
        LOG_ERROR << "conflicting set and reset values for DFF (" << gid + 1 << ")\n"
                  << mask64ToBinary(set1_mask) << " set1\n"
                  << mask64ToBinary(rst1_mask) << " rst1" << std::endl;
        break;
      }

      uint64_t slave_hold_val0 = _gmvPPtr[thread_id][gid].bit0();
      uint64_t slave_hold_val1 = _gmvPPtr[thread_id][gid].bit1();
      uint64_t mas_hold_val0 = _dffMasterValPPtr[thread_id][gid - _firstDffId].bit0();
      uint64_t mas_hold_val1 = _dffMasterValPPtr[thread_id][gid - _firstDffId].bit1();

      // init val0 & val1 as slave gmv
      val0 = _gmvPPtr[thread_id][gid].bit0();
      val1 = _gmvPPtr[thread_id][gid].bit1();

      uint64_t new_mas_v0 = mas_hold_val0;
      uint64_t new_mas_v1 = mas_hold_val1;

      // when clock==0, din-->master and slave holds its state.
      if (clk0_mask) {
        uint64_t dat_v0 = dat_val.bit0();
        uint64_t dat_v1 = dat_val.bit1();
        new_mas_v0 = (mas_hold_val0 & ~clk0_mask) | (dat_v0 & clk0_mask);
        new_mas_v1 = (mas_hold_val1 & ~clk0_mask) | (dat_v1 & clk0_mask);
      }
      // when clock==1, master holds its state and master-->slave.
      if (clk1_mask) {
        val0 = (slave_hold_val0 & ~clk1_mask) | (mas_hold_val0 & clk1_mask);
        val1 = (slave_hold_val1 & ~clk1_mask) | (mas_hold_val1 & clk1_mask);
      }
      // when clock==x, if din==slave, then din-->master, else, then set master and slave to X.
      if (clkx_mask) {
        uint64_t dat_v0 = dat_val.bit0();
        uint64_t dat_v1 = dat_val.bit1();
        uint64_t eq_mask = (~(dat_v0 ^ slave_hold_val0)) & (~(dat_v1 ^ slave_hold_val1)) & clkx_mask;
        uint64_t neq_mask = ((dat_v0 ^ slave_hold_val0) | (dat_v1 ^ slave_hold_val1)) & clkx_mask;
        // din-->master
        new_mas_v0 = (new_mas_v0 & ~eq_mask) | (dat_v0 & eq_mask);
        new_mas_v1 = (new_mas_v1 & ~eq_mask) | (dat_v1 & eq_mask);
        // set master and slave to X
        new_mas_v0 |= neq_mask;
        new_mas_v1 &= ~(neq_mask);
        val0 |= neq_mask;
        val1 &= ~neq_mask;
      }

      // adjust bit values for enable set and enable reset
      if (set1_mask) {
        new_mas_v0 |= set1_mask;
        new_mas_v1 |= set1_mask;
        val0 |= set1_mask;
        val1 |= set1_mask;
      }
      if (rst1_mask) {
        new_mas_v0 &= (~rst1_mask);
        new_mas_v1 &= (~rst1_mask);
        val0 &= ~rst1_mask;
        val1 &= ~rst1_mask;
      }

      // adjust bit values for unknown set and unknown reset
      if (setx_mask) {
        uint64_t mas1_mask = mas_hold_val0 & mas_hold_val1;
        uint64_t masx_mask = setx_mask & (~mas1_mask);  // keep val1
        new_mas_v0 |= masx_mask;
        new_mas_v1 &= ~masx_mask;

        uint64_t val1_mask = slave_hold_val0 & slave_hold_val1;
        uint64_t x_mask = setx_mask & (~val1_mask);  // keep val1
        val0 |= x_mask;
        val1 &= ~x_mask;
      }
      if (rstx_mask) {
        uint64_t mas0_mask = ~(mas_hold_val0 | mas_hold_val1);
        uint64_t masx_mask = rstx_mask & (~mas0_mask);  // keep val0
        new_mas_v0 |= masx_mask;
        new_mas_v1 &= ~masx_mask;

        uint64_t val0_mask = ~(slave_hold_val0 | slave_hold_val1);
        uint64_t x_mask = rstx_mask & (~val0_mask);  // keep val0
        val0 |= x_mask;
        val1 &= ~x_mask;
      }

      Value master_new_val(new_mas_v0, new_mas_v1);
      int master_id = gid - _firstDffId;
      if (_dffMasterValPPtr[thread_id][master_id] != master_new_val) {
        if (_masterIsValChangedPPtr[thread_id][master_id] == 0) {
          ++_masterChangedGmvIdPPtr[thread_id][0];
          _masterChangedGmvIdPPtr[thread_id][_masterChangedGmvIdPPtr[thread_id][0]] = master_id;
          _masterIsValChangedPPtr[thread_id][master_id] = 1;
        }
        _dffMasterValPPtr[thread_id][master_id] = master_new_val;
      }
      break;
    }
    case T_DLA_PRI:  // assume port set, reset, clock and data
    {
      uint64_t set_val0 = _gmvPPtr[thread_id][predsThread(gid, thread_id)[0]].bit0();
      uint64_t set_val1 = _gmvPPtr[thread_id][predsThread(gid, thread_id)[0]].bit1();
      uint64_t rst_val0 = _gmvPPtr[thread_id][predsThread(gid, thread_id)[1]].bit0();
      uint64_t rst_val1 = _gmvPPtr[thread_id][predsThread(gid, thread_id)[1]].bit1();
      uint64_t clk_val0 = _gmvPPtr[thread_id][predsThread(gid, thread_id)[2]].bit0();
      uint64_t clk_val1 = _gmvPPtr[thread_id][predsThread(gid, thread_id)[2]].bit1();
      Value dat_val = _gmvPPtr[thread_id][predsThread(gid, thread_id)[3]];

      uint64_t set1_mask = set_val0 & set_val1;
      uint64_t rst1_mask = rst_val0 & rst_val1;
      uint64_t clk1_mask = clk_val0 & clk_val1;
      uint64_t clk0_mask = ~(clk_val0 | clk_val1);
      uint64_t x_mask = (set_val0 & (~set_val1)) | (rst_val0 & (~rst_val1)) | (clk_val0 & (~clk_val1));

      if (set1_mask & rst1_mask) {
        LOG_ERROR << "conflicting set and reset values for D latch (" << gid + 1 << ")\n"
                  << mask64ToBinary(set1_mask) << " set1\n"
                  << mask64ToBinary(rst1_mask) << " rst1" << std::endl;
        break;
      }

      uint64_t in0 = dat_val.bit0();
      uint64_t in1 = dat_val.bit1();
      val0 = _gmvPPtr[thread_id][gid].bit0();
      val1 = _gmvPPtr[thread_id][gid].bit1();

      // pass through data with clk1_mask
      val0 = (val0 & (~clk1_mask)) | (in0 & clk1_mask);
      val1 = (val1 & (~clk1_mask)) | (in1 & clk1_mask);

      // adjust bit values for set and reset
      if (set1_mask) {
        val0 |= set1_mask;
        val1 |= set1_mask;
      }
      if (rst1_mask) {
        val0 &= (~rst1_mask);
        val1 &= (~rst1_mask);
      }

      // set output as X-(1,0) with clkx_mask
      // remaining bits of ck0_mask are kept
      // also set output as X with setx_mask and rstx_mask
      if (x_mask) {
        val0 = val0 | x_mask;
        val1 = val1 & ~(x_mask);
      }
      break;
    }
    default: {
      LOG_ERROR << "gate type " << LUT_GTypeToName[typeThread(gid, thread_id)] << " is not supported.";
      break;
    }
  }
}

/**
 * @brief Eval gate for fault sim in vec mode.
 */
void Simulation::evalGateForFsimInVecMode(int gid, int thread_id, int num_faults_in_group, __m256i_wrapper& val0, __m256i_wrapper& val1)
{
  switch (typeThread(gid, thread_id)) {
    case T_INPUT: {
      // gmv index is the same as id
      val0 = _gmv256PPtr[thread_id][gid].bit0();
      val1 = _gmv256PPtr[thread_id][gid].bit1();
      break;
    }
    case T_TIE1:  // (1,1)
    {
      val0.int256 = _mm256_set1_epi64x(ALL_ONES_64);
      val1.int256 = _mm256_set1_epi64x(ALL_ONES_64);
      break;
    }
    case T_TIE0:  // (0,0)
    {
      val0.int256 = _mm256_setzero_si256();
      val1.int256 = _mm256_setzero_si256();
      break;
    }
    case T_TIEX:  // (1,0)
    {
      val0.int256 = _mm256_set1_epi64x(ALL_ONES_64);
      val1.int256 = _mm256_setzero_si256();
      break;
    }
    case T_TIEZ:  // (0,1)
    {
      val0.int256 = _mm256_setzero_si256();
      val1.int256 = _mm256_set1_epi64x(ALL_ONES_64);
      break;
    }
    case T_OUTPUT:
    case T_BUF:
    case T_BUF_FB: {
      Value256 pred_val = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      break;
    }
    case T_BUFZ: {
      Value256 pred_val = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();

      __m256i xz_mask = _mm256_xor_si256(pred_val.bit0().int256, pred_val.bit1().int256);
      // set bits in xz_mask to X (1, 0)
      val0.int256 = _mm256_or_si256(xz_mask, val0.int256);
      val1.int256 = _mm256_andnot_si256(xz_mask, val0.int256);  // not(xz_mask) & val0
      break;
    }
    case T_AND: {
      Value256 pred_val = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        pred_val = _gmv256PPtr[thread_id][pred_id];
        val0.int256 = _mm256_and_si256(pred_val.bit0().int256, val0.int256);
        val1.int256 = _mm256_and_si256(pred_val.bit1().int256, val1.int256);
      }
      break;
    }
    case T_NAND: {
      Value256 pred_val = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        pred_val = _gmv256PPtr[thread_id][pred_id];
        val0.int256 = _mm256_and_si256(pred_val.bit0().int256, val0.int256);
        val1.int256 = _mm256_and_si256(pred_val.bit1().int256, val1.int256);
      }
      __m256i tmp_val = val0.int256;
      val0.int256 = _mm256_xor_si256(val1.int256, _allOnes256);
      val1.int256 = _mm256_xor_si256(tmp_val, _allOnes256);
      break;
    }
    case T_OR: {
      Value256 pred_val = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        pred_val = _gmv256PPtr[thread_id][pred_id];
        val0.int256 = _mm256_or_si256(pred_val.bit0().int256, val0.int256);
        val1.int256 = _mm256_or_si256(pred_val.bit1().int256, val1.int256);
      }
      break;
    }
    case T_NOR: {
      Value256 pred_val = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        pred_val = _gmv256PPtr[thread_id][pred_id];
        val0.int256 = _mm256_or_si256(pred_val.bit0().int256, val0.int256);
        val1.int256 = _mm256_or_si256(pred_val.bit1().int256, val1.int256);
      }
      __m256i tmp_val = val0.int256;
      val0.int256 = _mm256_xor_si256(val1.int256, _allOnes256);
      val1.int256 = _mm256_xor_si256(tmp_val, _allOnes256);
      break;
    }
    case T_NOT: {
      Value256 pred_val = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0.int256 = _mm256_xor_si256(pred_val.bit1().int256, _allOnes256);
      val1.int256 = _mm256_xor_si256(pred_val.bit0().int256, _allOnes256);
      break;
    }
    case T_XOR: {
      Value256 pred_val = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        pred_val = _gmv256PPtr[thread_id][pred_id];
        __m256i tmp_v = _mm256_or_si256(
            _mm256_and_si256(_mm256_xor_si256(pred_val.bit0().int256, _allOnes256), _mm256_xor_si256(val0.int256, _allOnes256)),
            _mm256_and_si256(pred_val.bit1().int256, val1.int256));
        tmp_v = _mm256_xor_si256(tmp_v, _allOnes256);
        val1.int256 = _mm256_or_si256(_mm256_and_si256(_mm256_xor_si256(pred_val.bit0().int256, _allOnes256), val1.int256),
                                      _mm256_and_si256(pred_val.bit1().int256, _mm256_xor_si256(val0.int256, _allOnes256)));
        val0.int256 = tmp_v;
      }
      break;
    }
    case T_XNOR: {
      Value256 pred_val = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[0]];
      val0 = pred_val.bit0();
      val1 = pred_val.bit1();
      for (int pred_i = 1; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        pred_val = _gmv256PPtr[thread_id][pred_id];
        __m256i tmp_v = _mm256_or_si256(
            _mm256_and_si256(_mm256_xor_si256(pred_val.bit0().int256, _allOnes256), _mm256_xor_si256(val0.int256, _allOnes256)),
            _mm256_and_si256(pred_val.bit1().int256, val1.int256));
        tmp_v = _mm256_xor_si256(tmp_v, _allOnes256);
        val1.int256 = _mm256_or_si256(_mm256_and_si256(_mm256_xor_si256(pred_val.bit0().int256, _allOnes256), val1.int256),
                                      _mm256_and_si256(pred_val.bit1().int256, _mm256_xor_si256(val0.int256, _allOnes256)));
        val0.int256 = tmp_v;
        __m256i tmp_val = val0.int256;
        val0.int256 = _mm256_xor_si256(val1.int256, _allOnes256);
        val1.int256 = _mm256_xor_si256(tmp_val, _allOnes256);
      }
      break;
    }
    case T_BUS: {  // keep Z output as is
      val0.int256 = _mm256_setzero_si256();
      val1.int256 = _mm256_set1_epi64x(ALL_ONES_64);
      for (int pred_i = 0; pred_i < numPredsThread(gid, thread_id); ++pred_i) {
        int pred_id = predsThread(gid, thread_id)[pred_i];
        Value256 pred_val = _gmv256PPtr[thread_id][pred_id];
        val0.int256 = _mm256_or_si256(pred_val.bit0().int256, val0.int256);
        val1.int256 = _mm256_and_si256(pred_val.bit1().int256, val1.int256);
      }
      break;
    }
    case T_TRISTATEBUF: {
      val0.int256 = _mm256_set1_epi64x(ALL_ONES_64);
      val1.int256 = _mm256_setzero_si256();
      Value256 en_val = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[0]];
      Value256 i_val = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[1]];
      // e1: enable is 1; D passthrough
      __m256i e1_mask = _mm256_and_si256(en_val.bit0().int256, en_val.bit1().int256);
      val0.int256 = _mm256_or_si256(_mm256_and_si256(val0.int256, _mm256_xor_si256(e1_mask, _allOnes256)),
                                    _mm256_and_si256(i_val.bit0().int256, e1_mask));
      val1.int256 = _mm256_or_si256(_mm256_and_si256(val1.int256, _mm256_xor_si256(e1_mask, _allOnes256)),
                                    _mm256_and_si256(i_val.bit1().int256, e1_mask));
      // e0: enable 0; output Z (0,1)
      __m256i e0_mask = _mm256_or_si256(en_val.bit0().int256, en_val.bit1().int256);
      e0_mask = _mm256_xor_si256(e0_mask, _allOnes256);
      val0.int256 = _mm256_andnot_si256(e0_mask, val0.int256);  // output Z (0,1) with enable 0, not(e0_mask) & val0
      val1.int256 = _mm256_or_si256(e0_mask, val1.int256);
      break;
    }
    case T_TRISTATEINV: {
      val0.int256 = _mm256_set1_epi64x(ALL_ONES_64);
      val1.int256 = _mm256_setzero_si256();
      Value256 en_val = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[0]];
      Value256 i_val = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[1]];
      // e1: enable is 1; D inverted
      __m256i e1_mask = _mm256_and_si256(en_val.bit0().int256, en_val.bit1().int256);
      val0.int256 = _mm256_or_si256(_mm256_and_si256(val0.int256, _mm256_xor_si256(e1_mask, _allOnes256)),
                                    _mm256_andnot_si256(i_val.bit1().int256, e1_mask));  // not(ival.bit1) & e1_mask
      val1.int256 = _mm256_or_si256(_mm256_and_si256(val1.int256, _mm256_xor_si256(e1_mask, _allOnes256)),
                                    _mm256_andnot_si256(i_val.bit0().int256, e1_mask));  // not(ival.bit0) & e1_mask
      // e0: enable 0; output Z (0,1)
      __m256i e0_mask = _mm256_or_si256(en_val.bit0().int256, en_val.bit1().int256);
      e0_mask = _mm256_xor_si256(e0_mask, _allOnes256);
      val0.int256 = _mm256_andnot_si256(e0_mask, val0.int256);  // output Z (0,1) with enable 0, not(e0_mask) & val0
      val1.int256 = _mm256_or_si256(e0_mask, val1.int256);
      break;
    }
    case T_MUX_2:  // Assume port (sel, d0, d1)
    {
      // s1: selector is 1; s0: selector is 0; dq: d0 == d1
      // start with Xs (1,0)
      val0.int256 = _mm256_set1_epi64x(ALL_ONES_64);
      val1.int256 = _mm256_setzero_si256();

      Value256 val_sel = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[0]];

      // assign d0 to output if sel is all 0's
      __m256i s0_mask = _mm256_or_si256(val_sel.bit0().int256, val_sel.bit1().int256);
      s0_mask = _mm256_xor_si256(s0_mask, _allOnes256);
      Value256 val_d0 = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[1]];

      // assign d1 to output if sel is all 1's
      __m256i s1_mask = _mm256_and_si256(val_sel.bit0().int256, val_sel.bit1().int256);
      Value256 val_d1 = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[2]];

      // pass d0 for sel==0 and d0==d1
      // dq_mask stands for bit mask where d0 and d1 are equal
      __m256i dq_mask = _mm256_or_si256(_mm256_xor_si256(val_d0.bit0().int256, val_d1.bit0().int256),
                                        _mm256_xor_si256(val_d0.bit1().int256, val_d1.bit1().int256));
      dq_mask = _mm256_xor_si256(dq_mask, _allOnes256);
      __m256i s0_dq_mask = _mm256_or_si256(s0_mask, dq_mask);
      val0.int256 = _mm256_and_si256(
          _mm256_or_si256(_mm256_and_si256(val_d0.bit0().int256, s0_dq_mask), _mm256_xor_si256(s0_dq_mask, _allOnes256)), val0.int256);
      val1.int256 = _mm256_or_si256(_mm256_and_si256(val_d0.bit1().int256, s0_dq_mask), val1.int256);

      // pass d1 for sel==1
      val0.int256 = _mm256_and_si256(
          _mm256_or_si256(_mm256_and_si256(val_d1.bit0().int256, s1_mask), _mm256_xor_si256(s1_mask, _allOnes256)), val0.int256);
      val1.int256 = _mm256_or_si256(_mm256_and_si256(val_d1.bit1().int256, s1_mask), val1.int256);

      break;
    }
    /* assume port set, reset, clock and data
    /    DFF primitive assumes master-slave flop structure.
    /    Signal 'set', 'reset' are asynchronous and affect the gmvs of
    /      master & slave D-latches immediately.
    /    gmvIndex(DFF) stores the gmv of the slave D-latch which is used
    /      to drive its fanout gates.
    /    gmvIndex(DFF) + 1 stores the gmv of the master D-latch that is
    /      used to drive the slave D-latch.
    */
    case T_DFF_PRI: {
      __m256i set_val0 = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[0]].bit0().int256;
      __m256i set_val1 = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[0]].bit1().int256;
      __m256i rst_val0 = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[1]].bit0().int256;
      __m256i rst_val1 = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[1]].bit1().int256;
      __m256i clk_val0 = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[2]].bit0().int256;
      __m256i clk_val1 = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[2]].bit1().int256;
      Value256 dat_val = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[3]];

      __m256i set1_mask = _mm256_and_si256(set_val0, set_val1);
      __m256i setx_mask = _mm256_and_si256(set_val0, _mm256_xor_si256(set_val1, _allOnes256));
      __m256i rst1_mask = _mm256_and_si256(rst_val0, rst_val1);
      __m256i rstx_mask = _mm256_and_si256(rst_val0, _mm256_xor_si256(rst_val1, _allOnes256));
      __m256i clk1_mask = _mm256_and_si256(clk_val0, clk_val1);
      __m256i clk0_mask = _mm256_xor_si256(_mm256_or_si256(clk_val0, clk_val1), _allOnes256);
      __m256i clkx_mask = _mm256_and_si256(clk_val0, _mm256_xor_si256(clk_val1, _allOnes256));

      if (_mm256_testz_si256(set1_mask, rst1_mask) != 1) {  // at least one bit both set and rst are all 1
        LOG_ERROR << "conflicting set and reset values for DFF (" << gid + 1 << ")\n";
        // << mask64ToBinary(set1_mask) << " set1\n"
        // << mask64ToBinary(rst1_mask) << " rst1" << std::endl;
        break;
      }

      __m256i slave_hold_val0 = _gmv256PPtr[thread_id][gid].bit0().int256;
      __m256i slave_hold_val1 = _gmv256PPtr[thread_id][gid].bit1().int256;
      __m256i mas_hold_val0 = _dffMasterVal256PPtr[thread_id][gid - _firstDffId].bit0().int256;
      __m256i mas_hold_val1 = _dffMasterVal256PPtr[thread_id][gid - _firstDffId].bit1().int256;

      // init val0 & val1 as slave gmv
      val0 = _gmv256PPtr[thread_id][gid].bit0();
      val1 = _gmv256PPtr[thread_id][gid].bit1();

      __m256i_wrapper new_mas_v0, new_mas_v1;
      new_mas_v0.int256 = mas_hold_val0;
      new_mas_v1.int256 = mas_hold_val1;

      // when clock==0, din-->master and slave holds its state.
      if (_mm256_testz_si256(clk0_mask, _allOnes256) == 0) {  // at least one bit == 1
        __m256i dat_v0 = dat_val.bit0().int256;
        __m256i dat_v1 = dat_val.bit1().int256;
        new_mas_v0.int256 = _mm256_or_si256(_mm256_and_si256(mas_hold_val0, _mm256_xor_si256(clk0_mask, _allOnes256)),
                                            _mm256_and_si256(dat_v0, clk0_mask));
        new_mas_v1.int256 = _mm256_or_si256(_mm256_and_si256(mas_hold_val1, _mm256_xor_si256(clk0_mask, _allOnes256)),
                                            _mm256_and_si256(dat_v1, clk0_mask));
      }
      // when clock==1, master holds its state and master-->slave.
      if (_mm256_testz_si256(clk1_mask, _allOnes256) == 0) {  // at least one bit == 1
        val0.int256 = _mm256_or_si256(_mm256_and_si256(slave_hold_val0, _mm256_xor_si256(clk1_mask, _allOnes256)),
                                      _mm256_and_si256(mas_hold_val0, clk1_mask));
        val1.int256 = _mm256_or_si256(_mm256_and_si256(slave_hold_val1, _mm256_xor_si256(clk1_mask, _allOnes256)),
                                      _mm256_and_si256(mas_hold_val1, clk1_mask));
      }
      // when clock==x, if din==slave, then din-->master, else, then set master and slave to X.
      if (_mm256_testz_si256(clkx_mask, _allOnes256) == 0) {  // at least one bit == 1
        __m256i dat_v0 = dat_val.bit0().int256;
        __m256i dat_v1 = dat_val.bit1().int256;
        __m256i eq_mask = _mm256_and_si256(_mm256_and_si256(_mm256_xor_si256(_mm256_xor_si256(dat_v0, slave_hold_val0), _allOnes256),
                                                            _mm256_xor_si256(_mm256_xor_si256(dat_v1, slave_hold_val1), _allOnes256)),
                                           clkx_mask);
        __m256i neq_mask = _mm256_and_si256(
            _mm256_or_si256(_mm256_xor_si256(dat_v0, slave_hold_val0), _mm256_xor_si256(dat_v1, slave_hold_val1)), clkx_mask);
        // din-->master
        new_mas_v0.int256 = _mm256_or_si256(_mm256_and_si256(new_mas_v0.int256, _mm256_xor_si256(eq_mask, _allOnes256)),
                                            _mm256_and_si256(dat_v0, eq_mask));
        new_mas_v1.int256 = _mm256_or_si256(_mm256_and_si256(new_mas_v1.int256, _mm256_xor_si256(eq_mask, _allOnes256)),
                                            _mm256_and_si256(dat_v1, eq_mask));
        // set master and slave to X
        new_mas_v0.int256 = _mm256_or_si256(new_mas_v0.int256, neq_mask);
        new_mas_v1.int256 = _mm256_and_si256(new_mas_v1.int256, _mm256_xor_si256(neq_mask, _allOnes256));
        val0.int256 = _mm256_or_si256(val0.int256, neq_mask);
        val1.int256 = _mm256_and_si256(val1.int256, _mm256_xor_si256(neq_mask, _allOnes256));
      }

      // adjust bit values for enable set and enable reset
      if (_mm256_testz_si256(set1_mask, _allOnes256) == 0) {  // at least one bit == 1
        new_mas_v0.int256 = _mm256_or_si256(set1_mask, new_mas_v0.int256);
        new_mas_v1.int256 = _mm256_or_si256(set1_mask, new_mas_v1.int256);
        val0.int256 = _mm256_or_si256(set1_mask, val0.int256);
        val1.int256 = _mm256_or_si256(set1_mask, val1.int256);
      }
      if (_mm256_testz_si256(rst1_mask, _allOnes256) == 0) {  // at least one bit == 1
        new_mas_v0.int256 = _mm256_and_si256(_mm256_xor_si256(rst1_mask, _allOnes256), new_mas_v0.int256);
        new_mas_v1.int256 = _mm256_and_si256(_mm256_xor_si256(rst1_mask, _allOnes256), new_mas_v1.int256);
        val0.int256 = _mm256_and_si256(_mm256_xor_si256(rst1_mask, _allOnes256), val0.int256);
        val1.int256 = _mm256_and_si256(_mm256_xor_si256(rst1_mask, _allOnes256), val1.int256);
      }

      // adjust bit values for unknown set and unknown reset
      if (_mm256_testz_si256(setx_mask, _allOnes256) == 0) {  // at least one bit == 1
        __m256i mas1_mask = _mm256_and_si256(mas_hold_val0, mas_hold_val1);
        __m256i masx_mask = _mm256_and_si256(setx_mask, _mm256_xor_si256(mas1_mask, _allOnes256));  // keep val1
        new_mas_v0.int256 = _mm256_or_si256(new_mas_v0.int256, masx_mask);
        new_mas_v1.int256 = _mm256_and_si256(new_mas_v1.int256, _mm256_xor_si256(masx_mask, _allOnes256));

        __m256i val1_mask = _mm256_and_si256(slave_hold_val0, slave_hold_val1);
        __m256i x_mask = _mm256_and_si256(setx_mask, _mm256_xor_si256(val1_mask, _allOnes256));  // keep val1
        val0.int256 = _mm256_or_si256(val0.int256, x_mask);
        val1.int256 = _mm256_and_si256(val1.int256, _mm256_xor_si256(x_mask, _allOnes256));
      }
      if (_mm256_testz_si256(rstx_mask, _allOnes256) == 0) {  // at least one bit == 1
        __m256i mas0_mask = _mm256_xor_si256(_mm256_or_si256(mas_hold_val0, mas_hold_val1), _allOnes256);
        __m256i masx_mask = _mm256_and_si256(rstx_mask, _mm256_xor_si256(mas0_mask, _allOnes256));  // keep val0
        new_mas_v0.int256 = _mm256_or_si256(new_mas_v0.int256, masx_mask);
        new_mas_v1.int256 = _mm256_and_si256(new_mas_v1.int256, _mm256_xor_si256(masx_mask, _allOnes256));

        __m256i val0_mask = _mm256_xor_si256(_mm256_or_si256(slave_hold_val0, slave_hold_val1), _allOnes256);
        __m256i x_mask = _mm256_and_si256(rstx_mask, _mm256_xor_si256(val0_mask, _allOnes256));  // keep val0
        val0.int256 = _mm256_or_si256(val0.int256, x_mask);
        val1.int256 = _mm256_and_si256(val1.int256, _mm256_xor_si256(x_mask, _allOnes256));
      }

      int master_id = gid - _firstDffId;
      Value256 master_new_val(new_mas_v0, new_mas_v1);
      if (_dffMasterVal256PPtr[thread_id][master_id] != master_new_val) {
        if (_masterIsValChangedPPtr[thread_id][master_id] == 0) {
          ++_masterChangedGmvIdPPtr[thread_id][0];
          _masterChangedGmvIdPPtr[thread_id][_masterChangedGmvIdPPtr[thread_id][0]] = master_id;
          _masterIsValChangedPPtr[thread_id][master_id] = 1;
        }
        _dffMasterVal256PPtr[thread_id][master_id] = master_new_val;
      }
      break;
    }
    case T_DLA_PRI:  // assume port set, reset, clock and data
    {
      __m256i set_val0 = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[0]].bit0().int256;
      __m256i set_val1 = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[0]].bit1().int256;
      __m256i rst_val0 = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[1]].bit0().int256;
      __m256i rst_val1 = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[1]].bit1().int256;
      __m256i clk_val0 = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[2]].bit0().int256;
      __m256i clk_val1 = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[2]].bit1().int256;
      Value256 dat_val = _gmv256PPtr[thread_id][predsThread(gid, thread_id)[3]];

      __m256i set1_mask = _mm256_and_si256(set_val0, set_val1);
      __m256i rst1_mask = _mm256_and_si256(rst_val0, rst_val1);
      __m256i clk1_mask = _mm256_and_si256(clk_val0, clk_val1);
      __m256i clk0_mask = _mm256_xor_si256(_mm256_or_si256(clk_val0, clk_val1), _allOnes256);
      __m256i x_mask = _mm256_or_si256(_mm256_and_si256(set_val0, _mm256_xor_si256(set_val1, _allOnes256)),
                                       _mm256_and_si256(rst_val0, _mm256_xor_si256(rst_val1, _allOnes256)));
      x_mask = _mm256_or_si256(_mm256_and_si256(clk_val0, _mm256_xor_si256(clk_val1, _allOnes256)), x_mask);

      if (_mm256_testz_si256(set1_mask, rst1_mask) != 1) {  // at least one bit both set and rst are all 1
        LOG_ERROR << "conflicting set and reset values for D latch (" << gid + 1 << ")\n";
        // << mask64ToBinary(set1_mask) << " set1\n"
        // << mask64ToBinary(rst1_mask) << " rst1" << std::endl;
        break;
      }

      __m256i in0 = dat_val.bit0().int256;
      __m256i in1 = dat_val.bit1().int256;
      val0 = _gmv256PPtr[thread_id][gid].bit0();
      val1 = _gmv256PPtr[thread_id][gid].bit1();

      // pass through data with clk1_mask
      val0.int256
          = _mm256_or_si256(_mm256_and_si256(val0.int256, _mm256_xor_si256(clk1_mask, _allOnes256)), _mm256_and_si256(in0, clk1_mask));
      val1.int256
          = _mm256_or_si256(_mm256_and_si256(val1.int256, _mm256_xor_si256(clk1_mask, _allOnes256)), _mm256_and_si256(in1, clk1_mask));

      // adjust bit values for set and reset
      if (_mm256_testz_si256(set1_mask, _allOnes256) == 0) {  // at least one bit == 1
        val0.int256 = _mm256_or_si256(set1_mask, val0.int256);
        val1.int256 = _mm256_or_si256(set1_mask, val1.int256);
      }
      if (_mm256_testz_si256(rst1_mask, _allOnes256) == 0) {  // at least one bit == 1
        val0.int256 = _mm256_and_si256(_mm256_xor_si256(rst1_mask, _allOnes256), val0.int256);
        val1.int256 = _mm256_and_si256(_mm256_xor_si256(rst1_mask, _allOnes256), val1.int256);
      }

      // set output as X-(1,0) with clkx_mask
      // remaining bits of ck0_mask are kept
      // also set output as X with setx_mask and rstx_mask
      if (_mm256_testz_si256(x_mask, _allOnes256) == 0) {  // at least one bit == 1
        val0.int256 = _mm256_or_si256(x_mask, val0.int256);
        val1.int256 = _mm256_and_si256(_mm256_xor_si256(x_mask, _allOnes256), val1.int256);
      }
      break;
    }
    default: {
      LOG_ERROR << "gate type " << LUT_GTypeToName[typeThread(gid, thread_id)] << " is not supported.";
      break;
    }
  }
}

/**
 * @brief Write fault in tmax format.
 */
void Simulation::writeFault(std::vector<std::string>& str_vec, Fault curr_fault)
{
  int fault_gid = curr_fault.gid();
  int fault_type = curr_fault.faultType();
  int fault_pin_id = curr_fault.pinId();
  std::string std_inst_name = _simGatesPtr[fault_gid].stdInstName();
  std::string fault_type_str = (fault_type == 1) ? "sa1" : "sa0";
  std::string fault_class_str = (curr_fault.faultClass() == EQ) ? "--" : LUT_FaultClassToString[curr_fault.faultClass()];

  str_vec.push_back(fault_type_str);
  str_vec.push_back("   ");
  str_vec.push_back(fault_class_str);
  str_vec.push_back("   ");
  if (_netlistManager->simNetlist().types()[fault_gid] == T_INPUT || _netlistManager->simNetlist().types()[fault_gid] == T_OUTPUT) {
    if (std_inst_name[0] == '\\') {
      str_vec.push_back(std_inst_name);
    } else {
      str_vec.push_back(std_inst_name);
    }
  } else {
    StdCellandIONode fault_std_node = _netlistManager->stdCellNetlist().stdCellandIONode(curr_fault.stdId());
    std::string port_name;
    if (curr_fault.stdPinId() < fault_std_node.outPortCnt()) {
      port_name = fault_std_node.outPortName(curr_fault.stdPinId());
    } else {
      port_name = fault_std_node.inPortName(curr_fault.stdPinId() - fault_std_node.outPortCnt());
    }
    if (std_inst_name[0] == '\\') {
      str_vec.push_back(std_inst_name);
      str_vec.push_back(".");
      str_vec.push_back(port_name);
    } else {
      str_vec.push_back(std_inst_name);
      str_vec.push_back(".");
      str_vec.push_back(port_name);
    }
  }
  str_vec.push_back("\n");
}

std::string Simulation::mask64ToBinary(uint64_t val)
{
  uint64_t mm = 0x8000000000000000;
  std::string str("");
  for (int i = 0; i < 64; i++) {
    if (val & mm) {
      str += "1";
    } else {
      str += "0";
    }
    mm >>= 1;
  }
  return str;
}
}  // namespace ifsim